Specifying datacontext to a generic Viewmodel - viewmodel

I have defined a viewmodel like
public class DataGridBaseViewModel<T>:ViewModelBase
{
ObservableCollection<T> DataCollection;
public abstract void Initialize();
}
public class Grid1ViewModel:DataGridBaseViewModel<MyModel1>
{
public void override Initialize()
{
DataCollection = new ObservableCollection<MyModel1>();
}
}
public class Grid2ViewModel:DataGridBaseViewModel<MyModel2>
{
public void override Initialize()
{
DataCollection = new ObservableCollection<MyModel2>();
}
}
Now I have 2 datagrids which should display MyModel1 and MyModel2. How can I specify the datacontext of the view.
Please help.
Thanks in advance

My views are usercontrols. I define my viewmodels in the viewmodel locator and then use the following in xaml to bind the view DATACONTEXT to the viewmodel:
<UserControl x:Class="FTC.View.EmployeeListView"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
mc:Ignorable="d"
DataContext="{Binding EmployeeList_VM, Source={StaticResource Locator}}">
So just use the above example to bind the datacontext of your datagrids to the viewmodels

Related

How do you bind a View Model to the View in XAML in Xamarin?

I've got a very basic view.
<ContentPage x:Class="ThetaRex.InvestmentManager.Merlin.Views.ScenarioSelectionPage"
Title="{Binding Title}"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns="http://xamarin.com/schemas/2014/forms">
<StackLayout>
<ListView ItemsSource="{Binding Items}"/>
</StackLayout>
<ContentPage/>
The code behind is also very simple:
namespace ThetaRex.InvestmentManager.Merlin.Views
{
using System.ComponentModel;
using ThetaRex.InvestmentManager.Merlin.ViewModels;
using Xamarin.Forms;
public partial class ScenarioSelectionPage : ContentPage
{
public ScenarioSelectionPage()
{
InitializeComponent();
this.BindingContext = this.ViewModel = new ScenarioSelectionViewModel();
}
public ScenarioSelectionViewModel ViewModel { get; set; }
protected override void OnAppearing()
{
base.OnAppearing();
ViewModel.LoadItemsCommand.Execute(null);
}
}
}
Coming from a pure MVVM environment in WPF and UWP, I want to bind the view to the viewmodel in XAML, not using the this.Binding = ViewModel in the code behind. I've tried:
<ContentPage x:Class="ThetaRex.InvestmentManager.Merlin.Views.ScenarioSelectionPage"
xmlns:controls="clr-namespace:ThetaRex.InvestmentManager.Merlin.Controls"
BindingContext="{Binding ViewModel}"
Title="{Binding Title}"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns="http://xamarin.com/schemas/2014/forms">
But it didn't work. How do I bind to the ViewModel from XAML?
Note: I know that I can create a view model from scratch in XAML, but it doesn't use the same instance that the code behind in the view uses, so that's not an option.
If I understood what you want, the solution is build a ViewModelLocator like this:
ViewModelLocalizator Class
public static class ViewModelLocalizator
{
public static readonly BindableProperty AutoWireViewModelProperty =
BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocalizator), default(bool), propertyChanged: OnAutoWireViewModelChanged);
public static bool GetAutoWireViewModel(BindableObject bindable)
{
return (bool)bindable.GetValue(AutoWireViewModelProperty);
}
public static void SetAutoWireViewModel(BindableObject bindable, bool value)
{
bindable.SetValue(AutoWireViewModelProperty, value);
}
/// <summary>
/// VERIFY THE VIEW NAME AND ASSOCIATE IT WITH THE VIEW MODEL OF THE SAME NAME. REPLACING THE 'View' suffix WITH THE 'ViewModel'
/// </summary>
private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
{
if (!(bindable is Element view))
{
return;
}
var viewType = view.GetType();
var viewModelName = viewType.FullName.Replace(".Views.", ".ViewModels.").Replace("Page", "ViewModel");
var viewModelType = Type.GetType(viewModelName);
if (viewModelType == null) { return; }
var vmInstance = Activator.CreateInstance(viewModelType);
if (vmInstance != null)
{
view.BindingContext = vmInstance;
}
}
}
Using It on your View
<ContentPage x:Class="YourProject.Views.YourTestPage"
Title="{Binding Title}"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:viewModelBase="clr-namespace:YourProject.ViewModels;assembly=YouProject"
viewModelBase:ViewModelLocalizator.AutoWireViewModel="true"
>
<StackLayout>
<ListView ItemsSource="{Binding Items}"/>
</StackLayout>
<ContentPage/>

Pass OnAppearing to a ViewModel in Xamarin Forms MVVM?

I have business logic that loops around and does waits and other things. Currently this is in the code behind.
From what I have been able to read this is the wrong place and I should be placing this in the viewModel (correct me if wrong). If that's the case then should I have an OnAppearing method in my VM and if so how should I pass the OnAppearing to the View Model?
Currently my page OnAppearing looks like this:
protected async override void OnAppearing()
{
base.OnAppearing();
Title = Settings.mode.Text() + " Deck";
vm.LearnViewVisible = Settings.mode.IsLearn();
vm.PracticeViewVisible = Settings.mode.IsPractice();
vm.QuizViewVisible = Settings.mode.IsQuiz();
vm.QuizStartViewVisible = false;
If I am to be moving most of the business logic to the ViewModel then would that mean that all of this would move to an OnAppearing() method I create in the ViewModel?
Other way is using Behaviors.Forms from David Britch
...
<ContentPage.Behaviors>
<behaviors:EventHandlerBehavior EventName="Appearing">
<behaviors:InvokeCommandAction Command="{Binding PageAppearingCommand}" />
</behaviors:EventHandlerBehavior>
<behaviors:EventHandlerBehavior EventName="Disappearing">
<behaviors:InvokeCommandAction Command="{Binding PageDisappearingCommand}" />
</behaviors:EventHandlerBehavior>
</ContentPage.Behaviors>
...
Original
Or Xamarin Community Toolkit EventToCommandBehavior
<ContentPage.Behaviors>
<xct:EventToCommandBehavior
EventName="Appearing"
Command="{Binding PageAppearingCommand}" />
<xct:EventToCommandBehavior
EventName="Disappearing"
Command="{Binding PageDisappearingCommand}" />
</ContentPage.Behaviors>
Related Question: EventHandlerBehavior vs EventToCommandBehavior
This is how i link my Viewmodel. I would recommend setting up a ViewModelBase with : VModelActive and VModelInactive
Code Behind:
public partial class YourClass : ContentPage
{
ViewModelClass viewModelClass;
public YourClass()
{
InitializeComponent();
viewModelClass = new ViewModelClass();
this.BindingContext = viewModelClass;
}
protected override void OnAppearing()
{
base.OnAppearing();
viewModelClass.VModelActive(this, EventArgs.Empty);
}
protected override void OnDisappearing()
{
base.OnDisappearing();
viewModelClass.VModelInactive(this, EventArgs.Empty);
}
}
View Model
public override void VModelActive(Page sender, EventArgs eventArgs)
{
base.VModelActive(sender, eventArgs);
//your code
}
public override void VModelInactive(Page sender, EventArgs eventArgs)
{
base.VModelInactive(sender, eventArgs);
//your code
}
I prefer a pattern I first encountered in some Realm sample code.
A ViewModel base provides empty overrideable OnAppearing/Disappearing
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
internal virtual void OnAppearing() { }
internal virtual void OnDisappearing() { }
}
User classes descend from a base that conditionally invokes the VM.
public class BasePage : ContentPage
{
protected override void OnAppearing()
{
base.OnAppearing();
(BindingContext as BaseViewModel)?.OnAppearing();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
(BindingContext as BaseViewModel)?.OnDisappearing();
}
}
// used as
public class JournalEntryDetailsViewModel : BaseViewModel
Warning: if you change the base class like this you need to use it in the XAML - use a scoped version of BasePage instead of the <ContentPage top element.
Otherwise you will get an error [CS0263] Partial declarations of 'JournalEntriesPage' must not specify different base classes
<?xml version="1.0" encoding="UTF-8"?>
<v:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:v="clr-namespace:QuickJournal.Views"
x:Class="QuickJournal.Views.JournalEntriesPage"
Title="Journal"
x:Name="page">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddEntryCommand}" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
Here is example from my solution
public partial class TaskDetailsPage : MvvmContentPage
{
private readonly TaskDetailsViewModel _model;
public TaskDetailsPage()
{
InitializeComponent();
Shell.SetNavBarIsVisible(this, true);
Shell.SetTabBarIsVisible(this, false);
_model = BindingContext as TaskDetailsViewModel;
}
protected override string NavigationRoute => UniqeCodes.Routes.TaskDetailsPage;
protected override void OnAppearing()
{
_model.Init();
}
}

Xamarin ListView binding is not working

I have been trying to bind my ListView to my View model. The view model successfully retrieves 5 records from the database and the Listview seems to display 5 blank rows, however it is not showing binding for each field within each row.
I have spent a couple of days searching internet but I don't seem to be doing anything different. I was using master detail pages so I thought that it may be the issue so I set my Events page as first navigation page without master/detail scenario but to no avail. Please note that I am using Portable Ninject for my dependencies/IoC.
My App.Xamal.cs is is as follows:
public App (params INinjectModule[] platformModules)
{
InitializeComponent();
var eventsPage = new NavigationPage(new EventsPage());
//Register core services
Kernel = new StandardKernel(new MyAppCoreModule(), new MyAppNavModule(eventsPage.Navigation));
//Register platform specific services
Kernel.Load(platformModules);
//Get the MainViewModel from the IoC
eventsPage.BindingContext = Kernel.Get<EventsViewModel>();
((BaseViewModel)(eventsPage.BindingContext)).Init();
MainPage = eventsPage;
}
My EventsPage.Xaml is provided below:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Views.EventsPage"
Title="Events">
<ContentPage.Content>
<ListView x:Name="Events" ItemsSource="{Binding Events}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding EventID}" BackgroundColor="Red" TextColor="White"
FontAttributes="Bold" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
My EventsPage.xaml.cs is provided below:
namespace MyApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class EventsPage : ContentPage, IBaseViewFor<EventsViewModel>
{
public EventsPage ()
{
InitializeComponent ();
}
EventsViewModel _vm;
public EventsViewModel ViewModel
{
get => _vm;
set
{
_vm = value;
BindingContext = _vm;
}
}
}
}
My EventsViewModel is as follows, it successfully retrieves 5 records and OnPropertyChanged is fired for Events property:
namespace MyApp.ViewModels
{
public class EventsViewModel : BaseViewModel, IBaseViewModel
{
ObservableCollection<Event> _events;
readonly IEventDataService _eventDataService;
public ObservableCollection<Event> Events
{
get { return _events; }
set
{
_events = value;
OnPropertyChanged();
}
}
public EventsViewModel(INavService navService, IEventDataService eventDataService) : base(navService)
{
_eventDataService = eventDataService;
Events = new ObservableCollection<Event>();
}
public override async Task Init()
{
LoadEntries();
}
async void LoadEntries()
{
try
{
var events = await _eventDataService.GetEventsAsync();
Events = new ObservableCollection<Event>(events);
}
finally
{
}
}
}
}
My BaseViewModel is as follows:
namespace MyApp.ViewModels
{
public abstract class BaseViewModel : INotifyPropertyChanged
{
protected INavService NavService { get; private set; }
protected BaseViewModel(INavService navService)
{
NavService = navService;
}
bool _isBusy;
public bool IsBusy
{
get
{
return _isBusy;
}
set
{
_isBusy = value;
OnPropertyChanged();
OnIsBusyChanged();
}
}
protected virtual void OnIsBusyChanged()
{
}
public abstract Task Init();
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// Secod BaseViewModel abstract base class with a generic type that will be used to pass strongly typed parameters to the Init method
public abstract class BaseViewModel<TParameter> : BaseViewModel
{
protected BaseViewModel(INavService navService) : base(navService)
{
}
public override async Task Init()
{
await Init(default(TParameter));
}
public abstract Task Init(TParameter parameter);
}
}
IBaseViewModel is just a blank interface:
public interface IBaseViewModel
{
}
IBaseViewFor is given below:
namespace MyApp.ViewModels
{
public interface IBaseViewFor
{
}
public interface IBaseViewFor<T> : IBaseViewFor where T : IBaseViewModel
{
T ViewModel { get; set; }
}
}
My Event model is as follows:
namespace MyApp.Models
{
public class Event
{
public int EventID;
}
}
Finally, the image of the output, as you can see that 5 rows are created with red background but EventID is not binding in each row. I have checked the data and EventID is returned. I have even tried to manually add records into Events list but to no avail, see the manual code and image below:
async void LoadEntries()
{
try
{
Events.Add((new Event() { EventID = 1 }));
Events.Add((new Event() { EventID = 2 }));
Events.Add((new Event() { EventID = 3 }));
Events.Add((new Event() { EventID = 4 }));
Events.Add((new Event() { EventID = 5 }));
}
finally
{
}
}
I have spent a lot of time on it but unable to find a reason for this anomaly, can someone please cast a fresh eye and provide help!?
You can only bind to public properties - ie, you need a getter
public class Event
{
public int EventID { get; set; }
}

Xamarin forms : Content View doesn't display in content page

Am trying to Load a Content View in a Content page. When I run the code it doesn't hit my Content View. I am assigning two Bindable parameters from my content page.
Content Page:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:templates="clr-namespace:G2.Scanner.Views.Templates"
xmlns:viewModelBase="clr-namespace:G2.Scanner.ViewModels.Base;assembly=G2.Scanner"
xmlns:telerikPrimitives="clr-namespace:Telerik.XamarinForms.Primitives;assembly=Telerik.XamarinForms.Primitives"
xmlns:local ="clr-namespace:G2.Scanner.Views.Transaction"
x:Class="G2.Scanner.Views.Transaction.TransactionView"
viewModelBase:ViewModelLocator.AutoWireViewModel="true">
<local:GenerateScreen x:Name="generateScreen" ControllerName="{Binding ControllerName}" IsBusy="{Binding IsBusy}"/>
</ContentPage>
Content View:
public partial class GenerateScreen : ContentView
{
#region BindableProperties
public static readonly BindableProperty ControllerNameProperty = BindableProperty.Create("ControllerName", typeof(string), typeof(GenerateScreen));
public static readonly BindableProperty IsBusyProperty = BindableProperty.Create("IsBusy", typeof(bool), typeof(GenerateScreen));
#endregion
#region Properties
public string ControllerName
{
get { return (string)GetValue(ControllerNameProperty); }
set { SetValue(ControllerNameProperty, value); }
}
public bool IsBusy
{
get { return (bool)GetValue(IsBusyProperty); }
set { SetValue(IsBusyProperty, value); }
}
#endregion
public GenerateScreen()
{
InitializeComponent();
DesignScreenAsync();
}
public async void DesignScreenAsync()
{
IsBusy = true;
// Some Design code..
#region render
scroll = new ScrollView
{
Content = layout
};
TransactionElements.Children.Add(scroll);
#endregion
IsBusy = false;
}
Content View accepts two bindable property and is used at the time of load.
Content View Xaml:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="G2.Scanner.Views.Transaction.GenerateScreen">
<ContentView.Content>
<StackLayout x:Name="TransactionElements"/>
</ContentView.Content>
</ContentView>
I want this view to be rendered at the time content page is loaded.
I figured out that this works if I don't use bindable properties in content view. But as per My requirement ControllerName and IsBusy in ContentView Class Should be Bindable Property.
Any Help why it's not hitting this View.
Thank you all for taking your time. I actually found a solution for myself.
What actually went wrong was in two places.
The bindable properties will be only set after the initialization of the class. So if we call the DesignScreenAsync method in the constructor which takes the value of ControllerName will obviously have null. the solution is to call the method in ControllerName's PropertyChanged method.
Here is the code:
public static readonly BindableProperty ControllerNameProperty = BindableProperty.Create(nameof(ControllerName), typeof(string), typeof(GenerateScreen),default(string), propertyChanged: OnControllerPropertyChange);
private static void OnControllerPropertyChange(BindableObject bindable, object oldValue, object newValue)
{
((GenerateScreen)bindable).DesignScreenAsync();
}
Notice that I have used BindableObject to call the method. This is to call the method from the same instence.
The reason why contentView was not hitting, is that I didn't set a default value for the bool IsBusyProperty, so it is important to set the default value for a bindable property.
Here is the code:
public static readonly BindableProperty IsBusyProperty = BindableProperty.Create(nameof(IsBusy), typeof(bool), typeof(GenerateScreen), default(bool), defaultBindingMode: BindingMode.TwoWay);
Defining an async void method is most of the time a bad practice.
To avoid this and probably solve your problem, you have 2 solutions depending on your need.
1) If DesignScreen only builds your UI without intensive call, you must execute it synchronously:
public GenerateScreen()
{
InitializeComponent();
DesignScreenAsync();
}
public void DesignScreen()
{
IsBusy = true;
// Some Design code..
#region render
scroll = new ScrollView
{
Content = layout
};
TransactionElements.Children.Add(scroll);
#endregion
IsBusy = false;
}
2) If you have intensive calls that need to be awaited. Perform intensive calls and and update UI using Device.BeginInvokeOnMainThread:
public async void DesignScreenAsync()
{
IsBusy = true;
await ApiIntensiveCall();
// Some Design code..
#region render
Device.BeginInvokeOnMainThread(() =>
{
scroll = new ScrollView
{
Content = layout
};
TransactionElements.Children.Add(scroll);
#endregion
});
IsBusy = false;
}

OnBackButtonPressed not fired in ViewModel

I have a created a simple contentpage to test various events. When overriding OnBackButtonPressed in the ViewModel the event is not raised. According to xamarin the event will not be raised on iOS, but it should work on Android and WP. But I cant get it to work on these platforms either.
What am I missing?
TestPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestPage.TestPage"
Title="testPage">
</ContentPage>
TestPage.xaml.cs
public partial class TestPage : ContentPage
{
public TestPage ()
{
InitializeComponent();
this.BindingContext = new TestPageViewModel();
}
}
ViewModel
public class TestPageViewModel : ContentPage, INotifyPropertyChanged
{
public TestPageViewModel() { }
protected override bool OnBackButtonPressed()
{
// Do stuff
return base.OnBackButtonPressed();
}
}
Got an answer on the Xamarin forum. I have to put the OnBackPressed override in the codebehind of my xaml file, not in my view model:
public partial class MyPage2 : ContentPage
{
public MyPage2()
{
InitializeComponent();
this.BindingContext = new TestPageViewModel();
}
protected override bool OnBackButtonPressed()
{
// Do stuff
return base.OnBackButtonPressed();
}
}

Resources