So I'm pretty new to the whole MVVM thing. I pretty much understand the basics of it. I have an example that's working just find as it is but I'm trying to change it into the MVVM style. Im just trying out different examples so I can learn.
(LoginPage.xaml)
On the XAML side I have a label with a tap gesture. pretty single
<!-- Existing Tab -->
<Label x:Name="labelName"
Grid.Column="0"
Style="{StaticResource CenteredTextTitle}"
Text="Log In">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="SelectorOption_Tapped" />
</Label.GestureRecognizers>
</Label>
LoginPage.xaml.cs page
I can access it by creating a tapped function.
private async void SelectorOption_Tapped(object sender, EventArgs e)
{
if (!(sender is Xamarin.Forms.View view)) return;
int index = Grid.GetColumn(view);
...
}
now im trying to change that into MVVM..
So the XAML stays the same
on the ViewModel page
loginpageviewmodel.cs page
public LoginPageViewModel()
{
btnSubmit_Clicked = new Command(SubmitClicked);
}
public ICommand btnSubmit_Clicked { get; set; }
private void SubmitClicked(object parameter)
{
if (!(parameter is Xamarin.Forms.View view)) return;
var index = Grid.GetColumn(view);
}
i can run the function submitclicked. But i cant pass on the parameter. Its always null.
i want to get the column from the grid that i have based on what the user selects. it works fine the original way but if i try to change it to MVVM i cant pass the view form to get the getcolumn..
is there anyway i can pass that.?
The parameter comes from the CommandParameter you set in the xaml:
<StackLayout>
<!-- Place new controls here -->
<Label x:Name="myLabel" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" Text="Log In">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding btnSubmit_Clicked}" CommandParameter="{x:Reference myLabel}"/>
</Label.GestureRecognizers>
</Label>
</StackLayout>
In code behind:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new LoginPageViewModel();
}
}
public class LoginPageViewModel
{
public LoginPageViewModel()
{
btnSubmit_Clicked = new Command(SubmitClicked);
}
public ICommand btnSubmit_Clicked { get; set; }
private void SubmitClicked(object parameter)
{
if (!(parameter is Xamarin.Forms.View view)) return;
Console.WriteLine(parameter);
}
}
Related
I want to be able to disable a button in a custom header, a ContentView, in my Xamarin.Forms app so that I can warn users that they have unsaved data before navigating away from the ContentPage which contains it.
I have created a BindableProperty for the HeaderView which my containing page binds its view model. I was inspired by this article: Add Custom Controls with Binding Properties to Your Xamarin.Forms App
The XAML for the ContentView is here
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
x:Class="HeaderView"
x:Name="HeaderRoot">
<Grid RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="80"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Padding="20,20,20,0" ColumnSpacing="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="1.3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackLayout Grid.Column="0">
<FlexLayout HorizontalOptions="CenterAndExpand" VerticalOptions="Center">
<ImageButton x:Name="BackButton" TranslationY="-20" TranslationX="-20" BackgroundColor="Black" HeightRequest="80" WidthRequest="80" Source="angleleft.png" Padding="0,0,0,0" Margin="0,0,0,0" Clicked="OnBackClicked" IsEnabled="{Binding Source={x:Reference HeaderRoot}, Path=BindingContext.IsEnabled}" />
</FlexLayout>
</StackLayout>
<FlexLayout Grid.Column="1" JustifyContent="Center" AlignItems="Center">
<FlexLayout.TranslationY>
<OnPlatform x:TypeArguments="x:Double">
<On Platform="Android" Value="-15"/>
<On Platform="iOS" Value="0"/>
</OnPlatform>
</FlexLayout.TranslationY>
<Label x:Name="TitleLabel" FontFamily="{StaticResource TitleFont}" TextColor="White" FontSize="50" />
</FlexLayout>
</Grid>
</Grid>
</ContentView>
The related code behind for the ContentView is here
public partial class HeaderView : ContentView
{
public HeaderView()
{
InitializeComponent();
if (DesignMode.IsDesignModeEnabled)
return; // Avoid rendering exception in the editor.
//Argument is null because the Header does not need navigation information.
BindingContext = new HeaderViewModel(null);
}
public static BindableProperty CanNavigateProperty =
BindableProperty.Create(
propertyName: "CanNavigate",
returnType: typeof(bool),
declaringType: typeof(HeaderView),
defaultValue: true,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: HandleCanNavigatePropertyChanged);
private static void HandleCanNavigatePropertyChanged(
BindableObject bindable,
object oldValue,
object newValue)
{
HeaderView targetView = (HeaderView)bindable;
if (targetView != null)
targetView.BackButton.IsEnabled = (bool)newValue;
}
//...
public bool CanNavigate
{
get
{
return (bool)base.GetValue(CanNavigateProperty);
}
set
{
if(this.CanNavigate != value)
{
base.SetValue(CanNavigateProperty, value);
}
}
}
protected void OnBackClicked(object sender, EventArgs e)
{
if (CanNavigate)
{
this.Navigation.PopAsync();
}
}
}
Edit .
The view model is quite simple.
public class HeaderViewModel : ViewModelBase
{
public HeaderViewModel(INavigationService navigationService)
: base(navigationService)
{
UserDTO dto = (Prism.PrismApplicationBase.Current as App).CurrentUser;
UserName = string.Format("{0} {1}", dto.FirstName, dto.LastName);
}
private string userName;
public string UserName
{
get
{
return userName;
}
set
{
SetProperty<string>(ref userName, value);
}
}
}
I then have the following markup in the containing page
<mdaViews:HeaderView CanNavigate="{Binding IsSaved}" Title="COLLECTIONS" Grid.Row="0" />
I have verified that the property, IsSaved, does change its value. The binding works when used in the Text of a Label.
When I change the value of the IsSaved property, the Label will change from "true" to "false" as expected. However this same binding does not appear to change the CanNavigate value in my custom header. While debugging, the OnBackClicked handler always shows a value of CanNavigate == true. The propertyChanged event,HandleCanNavigatePropertyChanged, is never entered. If I am supposed to explicitly call this, I don't know how. If I am missing something or using the wrong approach, I would like to know.
Edit Some of the postings in SO seem to suggest that having a BindingContext set to a view model, might be part of the problem.
Here is such an example:
BindableProperty in ContentView not working. I'm not sure what I should do about the view model, as it is working as expected.
I found the answer in the comment, "The page using the ContentView sets its BindingContext which will then be used for all child elements, including the custom ContentView. If you set the context in the content view again, you will overwrite it." by #Krumelur, who was responding to this answer https://stackoverflow.com/a/39989721/117995.
When I set the BindingContext in my ContentView, I overwrote all that was available from the parent page. To fix this problem I moved all ViewModel processing to the code behind and removed the code where I set the BindingContext.
I am a beginner, so this should be an easy fix for someone with experience.
I am a veterinarian trying to build an app for vets to use. I am trying to learn via youtube and previous posts online, but I cannot find a solution for my roadblock:
I am trying to call a method already written in the App class:
Settings form binds a volume of onhand penicillin.
Usage form displays that volume, and asks the user to enter a volume used.
On button click I need the onhand volume to be 'updated' but i can't call the method in the App class (which has a get and set)
I have simplified for easier evaluation:
App code behind:
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace methods
{
public partial class App : Application
{
private const string penicillin = "penicillin";
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
}
public string Penicillin
{
get
{
if (Properties.ContainsKey(penicillin))
{
return Properties[penicillin].ToString();
}
return "";
}
set
{
Properties[penicillin] = value;
}
}
}
}
Settings Form:
<?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="methods.Settings">
<ContentPage.Content>
<StackLayout>
<Label Text="Penicillin Onhand" ></Label>
<Entry Text="{Binding Penicillin}" Keyboard="Numeric" ></Entry>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Settings Code behind:
using Xamarin.Forms;
namespace methods
{
public partial class Settings : ContentPage
{
public Settings()
{
InitializeComponent();
BindingContext = Application.Current;
}
}
}
Usage form:
<?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="methods.Usage">
<ContentPage.Content>
<StackLayout>
<Label Text="Penicillin Held" ></Label>
<Label x:Name="Held" Text="{Binding Penicillin}" ></Label>
<Label Text="Penicillin Used" ></Label>
<Entry x:Name="Using" Placeholder="ml" Keyboard="Numeric" ></Entry>
<Button Text="Use" Clicked="Used_Clicked"></Button>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Usage Code behind:
using Xamarin.Forms;
namespace methods
{
public partial class Usage : ContentPage
{
public Usage()
{
InitializeComponent();
BindingContext = Application.Current;
}
void Used_Clicked(object sender, System.EventArgs e)
{
var VUsed = double.Parse(Using.Text);
var VHeld = double.Parse(Held.Text);
var Remaining = VHeld - VUsed;
//how do i code the following? Error:'App' does not contain definition for Penicillin????
App.Penicillin.value = Remaining;
}
}
}
You don't need to create a new instance of App - Xamarin Forms already maintains a reference to the current instance
((App)Application.Current).Penicillin = Remaining;
In my XAML I define the binding context like this:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Japanese;assembly=Japanese"
xmlns:template="clr-namespace:Japanese.Templates"
xmlns:viewModels="clr-namespace:Japanese.ViewModels; assembly=Japanese"
x:Class="Japanese.Cards" Title="{Binding Title}">
<ContentPage.BindingContext>
<viewModels:CardsViewModel />
</ContentPage.BindingContext>
</ContentPage>
What I would like to know is how can I access this context in my C# back end.
public partial class Cards : ContentPage
{
public Cards()
{
InitializeComponent();
NavigationPage.SetBackButtonTitle(this, "Back");
}
protected override void OnAppearing()
{
// I want to set some properties of the view here
It seems you cannot give it an x:Name attribute like other elements in your XAML. In that case, your options are limited to declaring the object for your binding context in the code-behind, or referencing it from the BindingContext property.
For the latter approach, do it like this:
protected override void OnAppearing()
{
var cardsViewModel = BindingContext as CardsViewModel;
if (cardsViewModel == null)
return;
cardsViewModel.Property = Value;
}
Earlier answer for reference:
You should be able to give it a name like so:
<ContentPage.BindingContext>
<viewModels:CardsViewModel x:Name="cardsViewModel" />
</ContentPage.BindingContext>
This will effectively just create a declaration like this in generated code:
private CardsViewModel cardsViewModel;
You can now access it in your code-behind:
protected override void OnAppearing()
{
cardsViewModel.Property = Value;
}
In my xamarin forms app using Prism MVVM Framework, How should I use IoC containers?
There are no
Container.Resolve<Interface,Implentation>
I think we need to resolve the IoC and initialize in app.xaml.cs. But I am not able to find proper way to do it.
Can anyone help me out ?
UPDATE:
PAGE:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="CoC.Core.Views.MenuPage">
<ContentPage.Content>
<ListView x:Name="info"
SeparatorVisibility="None"
SeparatorColor="Transparent"
HasUnevenRows="false"
RowHeight="50" BackgroundColor="#485366" >
<ListView.ItemTemplate>
<DataTemplate>
<!--<ImageCell Text="{Binding Title}" ImageSource="{Binding IconSource}" /> -->
<ViewCell>
<!--<Label Text="{Binding Title}" />-->
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
ViewModel:
using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
using Prism.Navigation;
namespace CoC.Core.ViewModels
{
public class MenuPageViewModel : BindableBase , INavigationAware
{
IMenuList _menuList;
public ObservableCollection<MenuItem> ConductList { get; set; }
public void OnNavigatedFrom(NavigationParameters parameters)
{
//throw new NotImplementedException();
}
public void OnNavigatedTo(NavigationParameters parameters)
{
//throw new NotImplementedException();
}
public MenuPageViewModel(IMenuList MenuList)
{
_menuList = MenuList;
ConductList = _menuList.GetMenuList();
}
}
}
APP:
using Prism.Unity;
using CoC.Core.Views;
namespace CoC.Core
{
public partial class App : PrismApplication
{
protected override void OnInitialized()
{
InitializeComponent();
//NavigationService.NavigateAsync("MainPage?title=Hello%20from%20Xamarin.Forms");
NavigationService.NavigateAsync("MenuPage");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<MainPage>();
Container.RegisterTypeForNavigation<RootPage>();
Container.RegisterTypeForNavigation<MenuPage>();
}
}
}
When I run this code, I am getting error in Main.cs of IOS pro
Error:
Foundation.MonoTouchException
NSInternalLnconistencyException Reason: Application window are expected to have a root view controller at the end of the application.
What you are looking for is an extension method in Unity. You need to add the using Microsoft.Practices.Unity namespace to your code file. The you can register your services using Container.Register<IService,MyService>().
I created a Prism Unity App (from the prism extension pack - really nice), added 2 new views and set up basic navigation between the views. This all worked fine without any hiccups.
What I really want is to use DryIoc (company policy). So I proceeded to remove the unity packages (Unity and Prism.Unity.Forms) and installed the DryIoc Packages (DryIoc and Prism.DryIoc.Forms). Fixed the App.xaml to use the correct namespace (xmlns:prism="clr-namespace:Prism.DryIoc;assembly=Prism.DryIoc.Forms") and alos fixed all the other references to use DryIoc and not the Unity References.
This all compiles and runs without any exceptions. But when I debug it is clear that the AutoWireup does not work as expected (at least as I expect it to). The breakpoint in the ViewModel's constructor (or any other place) is not hit and the Title bindings does not pull through.
Is there a Configuration/Setup or reference that I am missing?
My Code:
App.xaml:
<?xml version="1.0" encoding="utf-8" ?><prism:PrismApplication xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.DryIoc;assembly=Prism.DryIoc.Forms"
x:Class="XamFormsPrism.App"></prism:PrismApplication>
App.xaml.cs:
public partial class App : Prism.DryIoc.PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void ConfigureContainer()
{
base.ConfigureContainer();
}
protected override void OnInitialized()
{
this.InitializeComponent();
this.NavigationService.NavigateAsync(NavigationLinks.MainPage);
}
protected override void RegisterTypes()
{
this.Container.RegisterTypeForNavigation<MainPage>();
this.Container.RegisterTypeForNavigation<ViewA>();
this.Container.RegisterTypeForNavigation<ViewB>();
}
}
MainPage.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"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="XamFormsPrism.Views.MainPage"
Title="MainPage"><StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Label Text="{Binding Title}" />
<Button Text="{Binding NavigateText}" Command="{Binding NavigateCommand}" /> </StackLayout></ContentPage>
MainPageViewModel.cs:
public class MainPageViewModel : BindableBase, INavigationAware
{
private INavigationService navigationService = null;
private string _title = "Jose Cuervo";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public DelegateCommand NavigateCommand { get; private set; }
public MainPageViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;
this.NavigateCommand = new DelegateCommand(this.navigate, this.canNavigate);
}
private void navigate()
{
this.navigationService.NavigateAsync("ViewA");
}
private bool canNavigate()
{
return true;
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
if (parameters.ContainsKey("title"))
Title = (string)parameters["title"] + " and Prism";
}
}
As seen above I use the standard naming convention of PageName & PageNameViewModel with the AutoWireUp set to True.
This all worked fine with Unity but I am missing something in DryIoc...but what escapes me.
I have searched the entire project, there is no reference or trace left of Unity.
Any help is much appreciated.
I finally had time again to have a look at this. If I explicit register the ViewModels in the container everything works fine. So I am guessing that there is either a rule missing for the DryIoC container or when using DryIoC you have to explicitly register the ViewModels.