I have Xamarin Forms app to show a deck of cards. The Deck is initialized with a static collection in the App class. (A breakpoint shows it is populated). The MainPage has a MainPageViewModel set as the BindingContext:
Public MainPage(){
InitilizeComponent();
BindingContext = new MainPageViewModel()
}
The MainPageViewModel has a simple property to retrieve the populated Deck.Cards:
public static IEnumerable<Card> Cards { get { return Deck.Cards; }}
This is used in a Carousel collection in the main page.
However, the view is empty! I see
`[0: ] Binding: 'Cards' property not found on 'mypackage.ViewModels.MainPageViewModel', target property 'Xamarin.Forms.CarouselView.ItemsSource'
Why is the property not found? It is public and IEnumerable.
Assuming that your Xaml currently has ItemsSource={Binding Cards}, then make Cards non-static and it'll work.
If you want to keep Cards a static property, use x:Static:
ItemsSource={x:Static vm:MainPageViewModel.Cards}
And make sure you declare the namespace: xmlns:vm="clr-namespace:mypackage.ViewModels" at the top of your Xaml file.
Change this:
public static IEnumerable<Card> Cards { get { return Deck.Cards; }}
to this:
public ObservableCollection<Card> Cards{ get {return new ObservableCollection<Card>(Deck.Cards.ToList()); }}
Hope this helps.
Related
I have a route that is registered using the following
Routing.RegisterRoute("SchoolHome", typeof(SchoolHomePage));
I have a page with a list of schools and when I tap a School it executes this just fine from the ViewModel Command
Shell.Current.GoToAsync($"SchoolHome?schoolid={selectedSchool.Id}");
Here is my SchoolHomePage.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SchoolHomePage : ContentPage
{
public SchoolHomePage()
{
InitializeComponent();
BindingContext = new SchoolHomeViewModel();
}
}
Here is a snippet of SchoolHomeViewModel.cs
[QueryProperty("School", "schoolid")]
public class SchoolHomeViewModel : BaseViewModel
{
string school;
public string School
{
get
{
return school;
}
set
{
SetProperty(ref school, Uri.UnescapeDataString(value));
}
}
So Shell doesn't map my QueryProperty to my property School and the page doesn't navigate. If I put the QueryProperty in the ContentPage's code behind and set the ViewModel's School property there, it will pass the value along but the page still doesn't navigate. It just stays on the page with the list of schools with the one I just tapped being selected.
I found the problem. I had an issue with a property in my viewmodel. Once I fixed that it worked.
i am struggling with the mvvm data binding. I am not using any framework for the mvvm, I got a very basic base class for my view models. I uploaded my example-app with my problem to GitHub, find the link below.
My problem:
I got a simple app with an tab menu. there are 2 tabs called "TabA" and "TabB". Both views have a simple view model. The view models are referencing a manager class which holds the data. The Manager class has to objects (objects of my datamodel-class which just contains a string and implements INotifyPropertyChanged) in an observablecollection. There is also a Property in the Manager which references the current choosen object (its just one of the 2 objects from the list).
There are 2 actions which can be done by "TabB". The first one works as expected. If you enter some new string into the entry an hit the first button, it updates the string of the current choosen object and updates the label in TabA.
What is not working? With the second Button in my "TabB" class you switch the value of the current choosen object in the Manager. In the debugger I can see that the value is changed, but the Label in "TabA" does not recognize that it has to update the value.
Can you help me?
https://github.com/dercdev/MVVM-Xamarin
With the help of Jason I came to something like this:
In my TabAViewModel I subscribed the event of the Manager:
public TabAViewModel()
{
_mgr = Manager.Instance;
_mgr.PropertyChanged += new PropertyChangedEventHandler(obj_PropertyChanged);
}
Then I raise the event:
private void obj_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("CurrentData");
}
Which updates the label of the view.
Is that okay or is there a "better" way to do it?
As far as I know, the better way is to use INotifyPropertyChanged. If you want to implement Notify, I think you need to implement INotifyPropertyChanged interface, you can create one class name ViewModelBase that inheriting INotifyPropertyChanged, like this:
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 you can call RaisePropertyChanged method to inotify when property changed,
private string _text;
public string Text
{
get
{
return _text;
}
set
{
_text = value;
RaisePropertyChanged("Text");
}
}
ObservableCollection implements INotifyPropertyChanged, allowing the collection to notify the user when the contents of the collection have changed - and specifically, what changed within the collection. For example, if you add an item to the collection, the CollectionChanged event will be raised with properties that tell you the index of the new item as well as including the item in a list.
So ObservableCollection _list don't need to call RaisePropertyChanged method.
https://learn.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.observablecollection-1.system-componentmodel-inotifypropertychanged-propertychanged?view=netframework-4.7.2
I have a problem...I have MvxGridView with Menu items and with ItemClick ShowMenuCommand
Like this:
private ICommand _showMenuCommand;
public ICommand ShowMenuCommand
{
get
{
_showMenuCommand = _showMenuCommand ?? new MvxCommand<Menu>(DoShowMenuCommand);
return _showMenuCommand;
}
}
private void DoShowMenuCommand(Menu menu)
{
ShowViewModel<MenuCardViewModel>(menu);
}
Menu contains some properties like header, name, image, etc.. but contains also List menuItems. When I debug and breakpoint in DoShowMenuCommand Menu has List of menuItems but when I debug and breakpoint in MenuCardViewModel in method Init:
public void Init(Menu menu)
{
// HERE..
}
So here Menu has everything but MenuItems list is null. I dont know why... some tips why everything is here but list is null?
MvvmCross serialises complex DTO's into JSON. I'm not sure how you have implemented this but you could try it this way and see if that helps:
private void DoShowMenuCommand(Menu menu)
{
ShowViewModel<MenuCardViewModel,Menu>(menu);
}
So the second generic is the model that you want to pass. The next step is to add the menu generic to the "MenuCardViewModel"
public class MenuCardViewModel : MvxViewModel<Menu>
This will require to implement the init method:
protected override Task Init(Menu menu)
{
}
If this doesn't work i would suggest diving into why your list is not serializable/deserializable.
I'm new to Xamarin and C#, so apologies in advance for any mistakes I make.
In my app, I have a list of plants. When a plant is selected, I have a detail view of info about the plant. In the detail view, I have a button that adds or removes the plant from a shopping list.
To implement this, I have a class named MyPlant, with a field called InCart, and a method ToggleInCart that the button calls.
(note that I didn't paste in some code to simplify this question as much as possible)
public class MyPlant : INotifyPropertyChanged
{
string name;
bool inCart;
...
public bool InCart
{
set
{
if (inCart != value)
{
inCart = value;
OnPropertyChanged("InCart");
}
}
get { return inCart; }
}
public ICommand ToggleCartStatus
{
get
{
if (_toggleCartStatus == null)
{
_toggleCartStatus = new Command(() => InCart = !InCart);
}
return _toggleCartStatus;
}
I have another class called PlantList, which has a method PlantsInCart that uses LINQ to return an ObservableCollection of MyPlant where InCart is true.
public class PlantList : INotifyPropertyChanged
{
public ObservableCollection PlantsInCart
{
private set { }
get
{
ObservableCollection list = new ObservableCollection(myPlants.Where(i => i.InCart));
return list;
}
}
In my XAML, I have a ListView bound to PlantsInCart.
Everything works as I want EXCEPT when I remove the selected plant, the list doesn't update to show the plant is missing even though the data underneath it is correctly updated. If I refresh the list by going to a different page and coming back, then the list shows the right plants.
I suspect this doesn't work because the change in the InCart field isn't bubbling up high enough to that the ListView hears that it is supposed to update.
Can anybody advise me on the proper way to implement this kind of feature? In other words, how should you implement a scenario where you have a list that should update when a property of an item in the list changes?
I'm just switching a project across to mvvmlight and trying to do things "the right way"
I've got a simple app with a listbox
When an item is selected in the listbox, then I've hooked up a RelayCommand
This RelayCommand causes a call on an INavigationService (http://geekswithblogs.net/lbugnion/archive/2011/01/06/navigation-in-a-wp7-application-with-mvvm-light.aspx) which navigates to a url like "/DetailPage.xaml?DetailId=12"
The DetailPage.xaml is then loaded and ... this is where I'm a bit unsure...
how should the DetailPage get hooked up to a DetailView with DetailId of 12?
should I do this in Xaml somehow using a property on the ViewLocator?
should I do this in the NavigatedTo method?
Please feel free to point me to a full sample - sure this has been done a (hundred) thousand times before, but all the blogs and tutorials seem to be skipping this last trivial detail (focussing instead on the messaging and on the ioc on on the navigationservice)
Thanks!
The only place you can retrieve the URL parameter is in the view. So since your view is likely depending on it, you should fetch it in the OnNavigatedTo method.
Then, you should pass it along to your viewmodel, either using messaging (to expensive if you ask me), or by referring to your datacontext (which is the viewmodel I presume), and execeuting a method on that.
private AddTilePageViewModel ViewModel
{
get
{
return DataContext as AddTilePageViewModel;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var postalCode = NavigationContext.TryGetKey("PostalCode");
var country = NavigationContext.TryGetStringKey("Country");
if (postalCode.HasValue && string.IsNullOrEmpty(country) == false)
{
ViewModel.LoadCity(postalCode.Value, country);
}
base.OnNavigatedTo(e);
}
I'm using some special extensions for the NavigationContext to make it easier.
namespace System.Windows.Navigation
{
public static class NavigationExtensions
{
public static int? TryGetKey(this NavigationContext source, string key)
{
if (source.QueryString.ContainsKey(key))
{
string value = source.QueryString[key];
int result = 0;
if (int.TryParse(value, out result))
{
return result;
}
}
return null;
}
public static string TryGetStringKey(this NavigationContext source, string key)
{
if (source.QueryString.ContainsKey(key))
{
return source.QueryString[key];
}
return null;
}
}
}
Create a new WindowsPhoneDataBound application, it has an example of how to handle navigation between views. Basically you handle the navigation part in your view, then set the view's DataContext accord to the query string. I think it plays nicely with the MVVM pattern since your ViewModels don't have to know anything about navigation (which IMO should be handled at the UI level).