What do I need to do in my view to make Prism.Uno work with x:Bind? - prism

I come from a WPF / Prism background but I really like what X:Bind offers. How do I get x:Bind to work with my ViewModel when using Prism.Uno?
I have prism:ViewModelLocator.AutoWireViewModel="True" but I seem to be missing something in my understanding of how it works when designing.
Thanks
G

The use of x:Bind requires the binding path to be rooted in the View.
To use the DataContext, you'll need make it available typed through the view, like this:
public partial class MyControl : INotifyPropertyChanged
{
#if !HAS_UNO
// Uno already defines this event (it will be removed
// in the future to be aligned properly with WinUI)
public event PropertyChangedEventHandler PropertyChanged;
#endif
public MainPage()
{
this.InitializeComponent();
DataContextChanged +=
(s, e) => PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(nameof(ViewModel)));
}
public MyViewModel ViewModel => DataContext as MyViewModel;
}

Related

Create Behavior from Map Property in Control - Xamarin Forms MVVM Prism ArcGIS

I'm trying to show a callout when navigating from View A (form view) to View B (map view). I pass the record detail for the callout from View A to View B and it's bound to viewmodel of View B.
I would like to access the LoadStatusChanged event on the Map property of the Esri MapView control while adhering to MVVM architecture. Here is what my control looks like:
<esri:MapView x:Name="mapViewMain"
Map="{Binding MainMap}"
Grid.Row="0"
InteractionOptions="{Binding MapViewOptions}"
GraphicsOverlays="{Binding GraphicsOverlays}">
<esri:MapView.Behaviors>
<bh:ShowCalloutOnTapBehavior CalloutClickCommand="{Binding GoToDetailCommand}" />
<bh:ShowCalloutOnDataReceivedBehavior MeterMasterRequest="{Binding RequestParameters}" Map="{Binding MainMap}" />
</esri:MapView.Behaviors>
</esri:MapView>
I think I need to create a behavior that will take in the Map, wait for it to finish load, then show callout (results of MeterMasterRequest) on MapView (the ShowCallout method is on MapView control).
public class ShowCalloutOnDataReceivedBehavior : BehaviorBase<MapView>
{
public static readonly BindableProperty MeterMasterRequestProperty =
BindableProperty.Create(nameof(MeterMasterRequest), typeof(MeterMasterRequest), typeof(ShowCalloutOnDataReceivedBehavior));
public static readonly BindableProperty MapProperty =
BindableProperty.Create(nameof(Map), typeof(Map), typeof(ShowCalloutOnDataReceivedBehavior));
public MeterMasterRequest MeterMasterRequest
{
get { return (MeterMasterRequest)GetValue(MeterMasterRequestProperty); }
set { SetValue(MeterMasterRequestProperty, value); }
}
public Map Map
{
get { return (Map)GetValue(MapProperty); }
set { SetValue(MapProperty, value); }
}
How can I bind to the Map event from here? I don't know how I can get BehaviorBase to be of type Map. I seem to only be able to set behaviors at MapView level.
You can access the Map from the MapView.Map property and the MapView is passed in to the OnAttachedTo and OnDetachingFrom methods of the Behavior class. I see you have a BehaviorBase, which hopefully has the OnAttachedTo and OnDetachingFrom overrides still marked as protected virtual so you can override them in your ShowCalloutOnDataReceivedBehavior class
Override the OnAttachedTo method and then subscribe to the LoadStatusChanged event, override OnDetachingFrom so you can unsubscribe like so:
protected override void OnAttachedTo(MapView bindable)
{
base.OnAttachedTo(bindable);
bindable.Map.LoadStatusChanged += Map_LoadStatusChanged;
}
protected override void OnDetachingFrom(MapView bindable)
{
base.OnDetachingFrom(bindable);
bindable.Map.LoadStatusChanged -= Map_LoadStatusChanged;
}
private void Map_LoadStatusChanged(object sender, Esri.ArcGISRuntime.LoadStatusEventArgs e)
{
// Do stuff when LoadStatusChanged event is fired on the Map
}

Is it okay to include methods in a view model or should they be placed in another part of the code?

My Xamarin Forms ViewModel looks like this:
public class CFSPageViewModel : BaseViewModel
{
#region Constructor
public CFSPageViewModel()
{
PTBtnCmd = new Command<string>(PTBtn);
OnTappedCmd = new Command<string>(OnTapped);
}
#endregion
# region Commands
public ICommand PTBtnCmd { get; set; }
public ICommand OnTappedCmd { get; }
#endregion
#region Methods
private void OnTapped(string btnText)
{
Utils.SetState(btnText, CFS, SET.Cfs);
CFSMessage = Settings.cfs.TextLongDescription();
}
private void PTBtn(string btnText)
{
Utils.SetState(btnText, PT);
SetLangVisible(btnText);
SetLangSelected(btnText);
CFSMessage = Settings.cfs.TextLongDescription();
}
}
I was previously sending a message with MessageCenter to my C# back end code but now have removed MessageCenter so the methods are part of the ViewModel.
Is this a safe thing to do? I heard that MessageCenter messages passing around between ViewModels for everything was not the best of solutions.
Note that here is the way I had been doing it before:
MyPageViewModel.cs
PTBtnCmd = new Command<Templates.WideButton>((btn) =>
MessagingCenter.Send<CFSPageViewModel, Templates.WideButton>(
this, "PTBtn", btn));
MyPage.xaml.cs
MessagingCenter.Subscribe<CFSPageViewModel, Templates.WideButton>(
this, "PTBtn", (s, btn) =>
{
Utils.SetState(btn.Text, vm.PT);
SetLangVisible(btn.Text);
SetLangSelected(btn.Text);
vm.CFSMessage = Settings.cfs.TextLongDescription();
});
Note that methods such as SetLangVisible were also in MyPage.xaml.cs
To add an event handler to your Buttonsimply:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyProject.Views.MyPage">
<ContentPage.Content>
<StackLayout>
<Button Text="PTBtn" Clicked="Handle_Clicked" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
In code behind:
namespace MyProject.Views
{
public partial class MyPage : ContentPage
{
public MyPage()
{
InitializeComponent();
}
void Handle_Clicked(object sender, EventArgs eventArgs)
{
((Button)sender).BackgroundColor = Color.Blue; // sender is the control the event occured on
// Here call your methods depending on what they do/if they are view related
/*
Utils.SetState(btn.Text, vm.PT);
SetLangVisible(btn.Text);
SetLangSelected(btn.Text);
vm.CFSMessage = Settings.cfs.TextLongDescription();
*/
}
}
}
All the events that can have a event handler assigned to it is listed in yellow with E:
The Command fires first and you can add CanExecute as a second parameter in the constructor - which will also stop both the command and the event handler from being executed.
I would also rename the Command to something like SelectLanguageCommand - to distinguish it from a ui action. That way you can disconnect the button from the command and connect the command to other ui - if you decide you want to change the view in the future. It would also be easier to understand when unit testing.
Is this a safe thing to do? I heard that MessageCenter messages passing around between ViewModels for everything was not the best of solutions.
You could register all your view models with DependencyService
public App()
{
InitializeComponent();
DependencyService.Register<AboutViewModel>();
DependencyService.Register<CFSPageViewModel>();
DependencyService.Register<MyPageViewModel>();
MainPage = new AppShell();
}
Set BindingContext of the views to the instances registered:
public AboutPage()
{
InitializeComponent();
BindingContext = DependencyService.Get<AboutViewModel>();
}
And get the ViewModel instance anywhere you need it. That way you don't have to deal with subscriptions as you need to when using MessagingCenter.
Wether it is safe or not - I am not sure.

Xamarin MVVM Databinding not refreshing

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

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.

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

Resources