Xamarin MVVM Databinding not refreshing - xamarin

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

Related

Xamarin Native, Binding actions to listview items

I would like to ask about bindings. What is the best approach to bind some actions in listview items in ios and android using xamarin in mvvm world. As I understand, we have few approaches.
1.
For every list item we have some Model, and to this model we have to add some Commands.
For example:
public class ItemModel
{
public string MyName { get; set; }
public ICommand RemoveCommand { get; set; }
}
Where in ViewModel we have SomeInitMethod
public class ViewModel
{
public ObservableCollection<ItemModel> Items {get;set;}
public async Task SomeInitMethod
{
Items = new ObservableCollection(await _myApiService.FetchItemsAsync());
foreach(var item in Items)
{
item.Command = new RelayCommand(RemoveItem);
}
}
public void RemoveItem(ItemModel item)
{
Items.Remove(item);
}
}
But I see a drawback in SomeInitMethod where we should set RemoveCommand. What if we should to set 2 or even more commands than we duplicate code in ListItemView(somehow we need to bind all these commands)?
Next approach is somehow handle events of remove/toggle buttons and others in Listview and then delegate this commands directly to ViewModel.
Example:
ContactsListView.ItemRemoveClicked += (ItemModel model) => ViewModel.RemoveItem
Advantages is: we no longer need to handle commands in ViewModel
Drawback is: we need every time to write custom ListView and support event handling in code-behind.
The last approach is to send ViewModel to ListItem to set Commands.
Example
somewhere we have method CreateListViewItem on the view, let's say on iOS.
private void InitTableView() {
TableView.RegisterNibForCellReuse(ItemViewCell.Nib, ItemViewCell.Key);
var source = new ObservableTableViewSource <ItemModel>
{
DataSource = ViewModel.Items,
BindCellDelegate = (cell, viewModel, index) =>
{
if (cell is ItemModel memberCell)
{
memberCell.BindViewModel(viewModel);
memberCell.RemoveItem = (item) => ViewModel.RemoveItem;
}
}
};
TableView.Source = source;
}
Advantages: we no longer need to have Commands in Model, and we don't need to setup this Commands in ViewModel.
Possibly, drawback is that we somehow need to have ViewModel reference.
In WPF or UWP you have DataContext, you can binding directly to ViewModel.
Which approach you use, maybe I miss something, and it would be perfect if you provide some examples or thoughts.
Thanks.

What is the proper way to have a ListView show a LINQ query and update dynamically?

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?

Validation for items in ObservableCollection bound to DataGrid when validation of one item of collection depends on other items

I am using MVVM and displaying some items on a DataGrid. My model is RecordingInfo and looks like:
public class RecordingInfo : IDataErrorInfo
{
public RecordingInfo(string fullDirectoryName, string recordingName, int recordingNumber)
{
FullDirectoryName = fullDirectoryName;
RecordingName = recordingName;
RecordingNumber = recordingNumber;
}
public string FullDirectoryName { get; internal set; }
public string RecordingName { get; set; }
public int RecordingNumber { get; internal set; }
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string propertyName]
{
get {
if (propertyName == "RecordingName")
{
if (this.RecordingName.Length < 2)
return "Recording Name must be at least two characters";
}
return null;
}
}
}
I end up with a collection of these RecordingInfo programmatically. The user is not allowed to do much with these but he/she can change the RecordingName subject to the name being 2 characters or more AND that the RecordingName must be unique. I.e. no changing it to match another RecordingName. I have taken care of the first requirement. It's the second one that is giving me grief.
For my ViewModel, I have
public class RecordingListViewModel : ViewModelBase//, IDataErrorInfo
{
private ObservableCollection<RecordingInfo> _recordings = null;
public RecordingListViewModel()
{
}
public ObservableCollection<RecordingInfo> Recordings
{
get
{
return _recordings;
}
}
// more stuff left off for brevity
In my view I bind the collection to a DataGrid and have:
<DataGrid ItemsSource="{Binding Path=Recordings}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Recording" IsReadOnly="False" EditingElementStyle="{StaticResource CellEditStyle}" ElementStyle="{StaticResource CellNonEditStyle}" Binding="{Binding RecordingName, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" >
</DataGridTextColumn>
...
</DataGrid.Columns>
My way of checking for 2 or more characters works great. But this doesn't work for checking that the user is not trying to give a recording an existing name. Presumably, I need to somehow handle this at the ViewModel layer since the ViewModel knows about all Recordings. I tried playing with having my ViewModel derive from IDataErrorInfo but the property indexer never gets called, which makes sense as it's the Observable collection and therefore the individual RecordingInfos that are bound. I also thought about doing something with a "Lost Focus" event, but DataGridTextColumn doesn't seem to have that. I would think this is a somewhat common problem: validation must take into account relationships between the items of the collection.
By the way, I'm not wedded to the IDataErrorInfo and I am not opposed to other changes in architecture. Please let me know if I can provide more details. I have tried to provide a minimal amount of code. Clearly, this is part of a much bigger project. Any advice is appreciated.
Thanks,
Dave
I would do the following
1) Make RecordingInfo implement INotifyPropertyChanged
2) Use a BindingList<> instead of ObservableCollection<>
In your viewmodel, subscribe to the BindingList.ListChanged Event. This event will fire when items are added and removed, but also when the top level properties on RecordingInfo changes. In the case of a property being changed, the ListChangedEventArgs.PropertyDescriptor property will contain the name of the property, if you want to run validation for just that property (be careful though, this can be null when the item as added/removed). You'll need to use the ListChangedType property to determine the reason of the event (E.x.: Reset means everything changed, ItemAdded means the item was added, but the ItemChanged means a property changed as occurred on an existing item.
You can have the parent ViewModel (that contains and creates your RecordingInfos) pass a name validation Func in their constructors for them to call when validating their name changes.

mvvmlight - what's the "proper way" of picking up url parameters for a view model

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).

How do I get access to the QueryString in Windows Phone 7 from a user control

I have a simple user control in Windows Phone 7 and I want to get access to the querystring collection from the user controls Constructor. I have tried many ways and cannot seem to get acess to the containing XAML's querystring collection.
Essentially I am navigating to the page and the my user control is going to access the querystring value to write the value back to the interface.
Am I missing adding an assembly or reference or something?
I am not sure you should be trying to get at the information from the page's constructor, as it won't necessairly get called every time you land on this page. A better approach is to override the OnNavigatedTo method inherited from PhoneApplicationPage. Looking a little more carefully at your question, you may be trying to do this within a control embedded in the page, in which case you need to get at the Page in order to obtain the navigation information.
Regardless, the NavigationContext property from the page has a QueryString parameter that you can use to access the information you're after.
The following example assumes I have a parameter named "Message" in the query string when navigating to this page:
public partial class MyPage : PhoneApplicationPage
{
// Constructor
public MyPage()
{
InitializeComponent();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
String navigationMessage;
if (NavigationContext.QueryString.TryGetValue("Message", out navigationMessage))
{
this.textBlock1.Text = navigationMessage;
}
}
}
Sorry about that - I started to get there, and thanks for the clarification. Your best bet then is to walk up the visual tree from your control to find the Page, then you can have at the NavigationContext. In my sample below, I have a button on a custom control within the page, whose click event finds the nav context and looks for a certain navigation parameter - I couldn't tell from the question or your follow-up what would drive the control to "want" to find the content of the query string.
(Note about getting info from the ctor follows the code below)
public partial class WindowsPhoneControl1 : UserControl
{
public WindowsPhoneControl1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
GetTheQueryString();
}
private void GetTheQueryString()
{
var result = "No Joy";
var page = FindRootPage(this);
if (page != null)
{
if (page.NavigationContext.QueryString.ContainsKey("Param"))
{
result = page.NavigationContext.QueryString["Param"];
}
}
queryStringText.Text = result;
}
private static PhoneApplicationPage FindRootPage(FrameworkElement item)
{
if (item != null && !(item is PhoneApplicationPage))
{
item = FindRootPage(item.Parent as FrameworkElement);
}
return item as PhoneApplicationPage;
}
}
Note that this won't work from the ctor because of how Xaml works...the Xml tag drives the ctor to be called, then properties are set as indicated, then it is added as a child/item/etc in its container. If you do need to get at the context ASAP using this "walk up the tree" technique, handle the Control's Loaded event, by which time the control does have a parent and a tree that can be walked...
public WindowsPhoneControl1()
{
InitializeComponent();
Loaded += WindowsPhoneControl1_Loaded;
}
private void WindowsPhoneControl1_Loaded(Object sender, RoutedEventArgs e)
{
GetTheQueryString();
}
I would add a property to the UserControl subclass that would be set by the page in its OnNavigatedTo() method.

Resources