I am having trouble getting bindings defined in a ControlTemplate to work against my model.
Notice in the below ControlTemplate, I am using a TemplateBinding to bind to a property called Count (olive label). I am using Parent.Count as prescribed by this article, but neither values of Parent.Count nor Count are working.
The following page uses the ControlTemplate. Just to prove my ViewModel works I have a gray Label bound to the Count property as well.
Notice the resulting screen. The gray label is showing the Count property. The olive label from the ControlTemplate is not showing anything.
How can I make the Label in the ControlTemplate show the Count property from the ViewModel?
VIEW MODEL
namespace SimpleApp
{
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
_count = 10;
Uptick = new Command(() => { Count++; });
}
private int _count;
public int Count
{
get { return _count; }
set
{
_count = value;
OnPropertyChanged("Count");
}
}
public ICommand Uptick { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
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:local="clr-namespace:SimpleApp"
x:Class="SimpleApp.MainPage"
ControlTemplate="{StaticResource ParentPage}">
<StackLayout>
<Button Command="{Binding Uptick}" Text="Increment Count" />
<Label Text="{Binding Count}" BackgroundColor="Gray" />
</StackLayout>
</ContentPage>
CODE BEHIND
Notice the BindingContext is set to MainViewModel here. I need to use my own ViewModel and not the code behind.
namespace SimpleApp
{
public partial class MainPage : ContentPage
{
public MainPage()
{
BindingContext = new MainViewModel();
InitializeComponent();
}
}
}
CONTROL TEMPLATE
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SimpleApp.App">
<Application.Resources>
<ResourceDictionary>
<ControlTemplate x:Key="ParentPage">
<StackLayout>
<Label Text="{TemplateBinding Parent.Count}" BackgroundColor="Olive" />
<ContentPresenter />
</StackLayout>
</ControlTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
On your ControlTemplate please use the following code:
<Label Text="{TemplateBinding BindingContext.Count}" BackgroundColor="Olive" />
It seems that BindingContext is not being automatically applied to your ContentPage child, maybe it could be a bug in Xamarin.
Related
I am trying to output a value from my object to two different entries. Both entries are on the same view but in different ContentPages as follows:
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="myApp.Views.ViewTabs.ViewHome"
xmlns:localTabs="clr-namespace:myApp.Views.ViewTabs"
xmlns:localObjPages="clr-namespace:myApp.Objects"
>
<ContentPage Title="PageOne">
<ContentPage.BindingContext>
<localObjPages:PagesObj/>
</ContentPage.BindingContext>
<ScrollView>
<StackLayout>
<Entry
x:Name="EntryOne" Text="{Binding BananaCount}"/>
<Entry
x:Name="EntryTwo" Text="{Binding BananaCount}"/>
</StackLayout>
</ScrollView>
</ContentPage>
<ContentPage Title="PageTwo">
<ContentPage.BindingContext>
<localObjPages:PagesObj/>
</ContentPage.BindingContext>
<ScrollView>
<StackLayout>
<Entry
x:Name="EntryThree" Text="{Binding BananaCount}"/>
</StackLayout>
</ScrollView>
</ContentPage>
</TabbedPage>
My Model:
public string BananaCount
{
get { return _bananaCount; }
set
{
if (_bananaCount != value)
{
_bananaCount = value;
NotifyPropertyChanged("BananaCount");
}
}
}
The object is updated and returned in EntryOne or in EntryTwo when I change it either in EntryOne or in EntryTwo. However, it is not updated in EntryThree. Why is this? Am I Binding this correctly? Thank you.
The object is updated and returned in EntryOne or in EntryTwo when I change it either in EntryOne or in EntryTwo. However, it is not updated in EntryThree. Why is this? Am I Binding this correctly?
Do one sample about TabbedPage, assign datasource for TabbedPage, not contentpage, that you can take a look:
<TabbedPage
x:Class="FormsSample.tabbedpage.TabbedPage6"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:FormsSample.tabbedpage">
<!--Pages can be added as references or inline-->
<ContentPage Title="PageOne">
<ScrollView>
<StackLayout>
<Entry x:Name="EntryOne" Text="{Binding str}" />
<Entry x:Name="EntryTwo" Text="{Binding str}" />
<Button
x:Name="btn1"
Clicked="btn1_Clicked"
Text="change data" />
</StackLayout>
</ScrollView>
</ContentPage>
<ContentPage Title="PageTwo">
<ScrollView>
<StackLayout>
<Entry x:Name="EntryThree" Text="{Binding str}" />
</StackLayout>
</ScrollView>
</ContentPage>
public partial class TabbedPage6 : TabbedPage
{
public tabclass tabc { get; set; }
public TabbedPage6()
{
InitializeComponent();
tabc = new tabclass();
this.BindingContext = tabc;
}
private void btn1_Clicked(object sender, EventArgs e)
{
tabc.str = "this is test!";
}
}
public class tabclass:ViewModelBase
{
private string _str;
public string str
{
get { return _str; }
set
{
_str = value;
RaisePropertyChanged("str");
}
}
}
The ViewModel is one class that implement INotifyPropertyChanged.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Trying to fire a command inside a stacklayout with itemssource. I wonder why the NavigateToProductListViewShopTappedCommand is not getting fired.
Tried multiple command approaches:
1)
Command="{Binding Source={RelativeSource AncestorType={x:Type local:MyShopsListViewModel}}, Path=NavigateToProductListViewShopTappedCommand}"
Command="{Binding BindingContext.NavigateToProductListViewShopTappedCommand, Source={x:Reference Page}}"
Command="{Binding Path=BindingContext.NavigateToProductListViewShopTappedCommand, Source={x:Reference Page}}"
None are not working
Code:
<?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:controls="clr-namespace:BoerPlaza.Controls.Shop"
xmlns:local="clr-namespace:BoerPlaza.ViewModels"
xmlns:behaviors="clr-namespace:BoerPlaza.Behaviors"
x:Class="BoerPlaza.Views.Shop.MyShopsPage"
x:Name="Page"
Title="Mijn winkels">
<ContentPage.Content>
<ScrollView>
<StackLayout Margin="{StaticResource margin-side-std}"
Padding="{StaticResource padding-top-bottom-std}"
BindableLayout.ItemsSource="{Binding Shops}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<controls:ShopCardTemplateView Shop="{Binding .}"
ControlTemplate="{StaticResource ShopCardTemplateView}">
<controls:ShopCardTemplateView.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1"
Command="{Binding Source={RelativeSource AncestorType={x:Type local:MyShopsListViewModel}}, Path=NavigateToProductListViewShopTappedCommand}"
CommandParameter="{Binding .}">
<!--Command="{Binding BindingContext.NavigateToProductListViewShopTappedCommand, Source={x:Reference Page}}"-->
</TapGestureRecognizer>
</controls:ShopCardTemplateView.GestureRecognizers>
</controls:ShopCardTemplateView>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
Code behind
public partial class MyShopsPage : ContentPage
{
private readonly MyShopsListViewModel _viewModel;
public MyShopsPage()
{
InitializeComponent();
BindingContext = _viewModel = new MyShopsListViewModel(App.ShopDataStore, App.DialogService);
_viewModel.LoadShopsOnUserIdCommand.Execute("B22698B8-42A2-4115-9631-1C2D1E2AC5F7");
}
protected override void OnAppearing()
{
base.OnAppearing();
_viewModel.OnAppearing();
}
}
ViewModel:
[QueryProperty(nameof(UserId), nameof(UserId))]
public class MyShopsListViewModel : BaseViewModel
{
private string _userId;
private ObservableCollection<ShopDbViewModel> _shops;
private readonly IShopDataStore _shopDataStore;
private readonly IDialogService _dialogService;
public ObservableCollection<ShopDbViewModel> Shops
{
get
{
return _shops;
}
set
{
_shops = value;
OnPropertyChanged(nameof(Shops));
}
}
public void OnAppearing()
{
IsBusy = true;
}
public ICommand LoadShopsOnUserIdCommand { get; set; }
public ICommand NavigateToProductListViewShopTappedCommand { get; set; }
public MyShopsListViewModel(IShopDataStore shopDataStore, IDialogService dialogService)
{
this._shopDataStore = shopDataStore;
this._dialogService = dialogService;
Shops = new ObservableCollection<ShopDbViewModel>();
LoadShopsOnUserIdCommand = new Command<string>(async (string userId) => await ExecuteLoadShopsOnUserId(userId));
NavigateToProductListViewShopTappedCommand = new Command<ShopDbViewModel>(async (ShopDbViewModel shop) => await ExecuteNavigateToProductListViewShopTappedCommandAsync(shop));
}
private async Task ExecuteNavigateToProductListViewShopTappedCommandAsync(ShopDbViewModel shop)
{
if (shop == null)
return;
await Shell.Current.GoToAsync($"{nameof(ProductsPage)}?{nameof(MyProductsListViewModel.ShopId)}={shop.Id}");
}
public string UserId
{
get
{
return _userId;
}
set
{
_userId = value;
LoadShopsOnUserIdCommand.Execute(value);
}
}
private async Task ExecuteLoadShopsOnUserId(string userId)
{
var current = Connectivity.NetworkAccess;
if (current == NetworkAccess.Internet)
{
try
{
Shops.Clear();
var shops = await _shopDataStore.GetShopOnUserIdAsync(userId);
foreach(var shop in shops)
{
Shops.Add(shop);
}
}
catch (Exception ex)
{
await _dialogService.ShowDialog(ex.Message, "An error has occurred", "OK");
}
finally
{
IsBusy = false;
}
}
else
{
await _dialogService.ShowDialog("No active internet connection", "Connection error", "OK");
IsBusy = false;
}
}
}
If you define the ICommand in the ViewModel directly , you could set the binding path like following
Command="{Binding Path=BindingContext.NavigateToProductListViewShopTappedCommand, Source={x:Reference Page}}"
I've found the problem
<?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:ffimage="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:customcontrols="clr-namespace:BoerPlaza.Controls"
x:Class="BoerPlaza.Controls.Shop.ShopCardTemplateView">
<ContentView.Resources>
<ControlTemplate x:Key="ShopCardTemplateView">
<!-- Card Header -->
<!-- for displaying products and categories on homepage -->
<StackLayout Spacing="1"
HorizontalOptions="FillAndExpand"
Margin="{StaticResource margin-card}">
<!-- On click - shows the product detail view page -->
<StackLayout.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" />
</StackLayout.GestureRecognizers>
<!-- Image frame -->
<Frame BackgroundColor="{StaticResource image-box-color}"
CornerRadius="0"
HasShadow="False"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
HeightRequest="100">
<!-- Product Image -->
....
As you can see this is the control template I'm using for the MyShopsPage. This is a shop card. Inside this shop card I already had an StackLayout.GestureRecognizers. Somehow when I was clicking on the control template, I was actually clicking on this.
I always thought everything flows from top to bottom in events, but this seems different. Something that is on top on something else does not mean anything in xaml.
I'm wanting to create a GUI that has a similar to what the following code generates, a scroll of frames.
However I want to be able to have a scroll of dynamic content frames, ideally in XAML and populated with an Item source. I don't think this is possible without creating a custom view based on itemsview from what I can see. ListView and CollectionView don't quite do what I want.
I think I need to use the preview CarouselView, I was wondering if there is a way of doing what I'm after without.
<?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="FlexTest.MainPage">
<ContentPage.Resources>
<Style TargetType="Frame">
<Setter Property="WidthRequest" Value="300"/>
<Setter Property="HeightRequest" Value="500"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="CornerRadius" Value="20"/>
</Style>
</ContentPage.Resources>
<ScrollView Orientation="Both">
<FlexLayout>
<Frame BackgroundColor="Yellow">
<FlexLayout Direction="Column">
<Label Text="Panel 1"/>
<Label Text="A Panel"/>
<Button Text="Click Me"/>
</FlexLayout>
</Frame>
<Frame BackgroundColor="OrangeRed">
<FlexLayout Direction="Column">
<Label Text="Panel 2"/>
<Label Text="Another Panel"/>
<Button Text="Click Me"/>
</FlexLayout>
</Frame>
<Frame BackgroundColor="ForestGreen">
<FlexLayout Direction="Column">
<Label Text="Panel 3"/>
<Label Text="A Third Panel"/>
<Button Text="Click Me"/>
</FlexLayout>
</Frame>
</FlexLayout>
</ScrollView>
</ContentPage>
Thanks
Andy.
Do you want to implement a scrollable view and each child contains multiple content that can be scrolled horizontally?
For this feature, try to display the CarouselView in a ListView.
Check the code:
<ListView ...>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<CarouselView>
<CarouselView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Tutorial about CarouselView:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/carouselview/introduction
Preface: I hope I understood your request correctly :)
If by dynamic content you mean having a dynamic ItemTemplate then you can try doing following:
Step One:
Define an ItemTemplateSelector, you can give it w.e name you want. In this class we will define what sort of templates we have, let us say we have the three which you defined: Yellow, OrangeRed, ForestGreen
public class FrameTemplateSelector : DataTemplateSelector {
public DataTemplate YellowFrameTemplate {get; set;}
public DataTemplate OrangeRedFrameTemplate {get; set;}
public DataTemplate ForestGreenFrameTemplate {get; set;}
public FrameTemplateSelector() {
this.YellowFrameTemplate = new DataTemplate(typeof (YellowFrame));
this.OrangeRedFrameTemplate = new DataTemplate(typeof (OrangeRedFrame));
this.ForestGreenFrameTemplate = new DataTemplate(typeof (ForestGreenFrame));
}
//This part is important, this is how we know which template to select.
protected override DataTemplate OnSelectTemplate(object item, BindableObject container) {
var model = item as YourViewModel;
switch(model.FrameColor) {
case FrameColorEnum .Yellow:
return YellowFrameTemplate;
case FrameColorEnum .OrangeRed:
return OrangeRedFrameTemplate;
case FrameColorEnum .ForestGreen:
return ForestGreenFrameTemplate;
default:
//or w.e other template you want.
return YellowFrameTemplate;
}
}
Step Two:
Now that we have defined our Template Selector let us go ahead and define our templates, in this case our Yellow, OrangeRed, and ForestGreen frames respectively. I will simply show how to make one of them since the others will follow the same paradigm excluding, with of course the color changing. Let's do the YellowFrame
In the XAML you will have:
YellowFrame.xaml:
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="YourNameSpaceGoesHere.YellowFrame">
<Frame BackgroundColor="Yellow">
<FlexLayout Direction="Column">
<Label Text="Panel 1"/>
<Label Text="A Panel"/>
<Button Text="Click Me"/>
</FlexLayout>
</Frame>
</StackLayout>
In the code behind:
YellowFrame.xaml.cs:
public partial class YellowFrame : StackLayout {
public YellowFrame() {
InitializeComponent();
}
}
Step Three
Now we need to create our ViewModel that we will use for our ItemSource that we will apply to FlexLayout, per the documentation for Bindable Layouts, any layout that "dervies from Layout" has the ability to have a Bindable Layout, FlexLayout is one of them.
So let us make the ViewModel, I will also create an Enum for the Color frame we want to render as I showed in the switch statement in step one, however, you can choose what ever means of deciding how to tell which template to load; this is just one possible example.
BaseViewModel.cs:
public abstract class BaseViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = ""){
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public virtual void CleanUp(){
}
}
ParentViewModel.cs:
public class ParentViewModel: BaseViewModel {
private ObservableCollection<YourViewModel> myViewModels {get; set;}
public ObservableCollection<YourViewModel> MyViewModels {
get { return myViewModels;}
set {
myViewModels = value;
OnPropertyChanged("MyViewModels");
}
}
public ParentViewModel() {
LoadData();
}
private void LoadData() {
//Let us populate our data here.
myViewModels = new ObservableCollection<YourViewModel>();
myViewModels.Add(new YourViewModel {FrameColor = FrameColorEnum .Yellow});
myViewModels.Add(new YourViewModel {FrameColor = FrameColorEnum .OrangeRed});
myViewModels.Add(new YourViewModel {FrameColor = FrameColorEnum .ForestGreen});
MyViewModels = myViewModels;
}
}
YourViewModel.cs:
public class YourViewModel : BaseViewModel {
public FrameColorEnum FrameColor {get; set;}
}
FrameColorEnum.cs:
public enum FrameColorEnum {
Yellow,
OrangeRed,
ForestGreen
}
We're almost there, so what we have done so far is we defined our view models that we will use on that page, the final step is to update our overall XAML where we will call our Template Selector. I will only update the snippets needed.
<ContentPage
...
**xmlns:views="your namespace where it was defined here,
normally you can just type the name of the Selector then have VS add the proper
namespace and everything"**
<ContentPage.Resources>
<!--New stuff below-->
<ResourceDictionary>
<views:FrameTemplateSelector x:Key="FrameTemplateSelector"/>
</ResourceDictionary>
</ContentPage.Resources>
<ScrollView Orientation="Both">
<FlexLayout BindableLayout.ItemsSource="{Binding MyViewModels, Mode=TwoWay}"
BindableLayout.ItemTemplateSelector ="{StaticResource FrameTemplateSelector}"/>
</ScrollView>
Live Picture:
I am using a bindable StackLayout to show a series of Entry bound to an ObservableCollection<string> (Addresses in the the viewModel down).
It is not a problem to show on the UI the content of the collection, but if I modify the content of any of the Entry, it does not get reflected back in the original ObservableCollection
Here is the view model:
public class MainViewModel
{
public ObservableCollection<string> Addresses { get; set; }
public ICommand AddCommand { get; private set; }
public MainViewModel()
{
AddCommand = new Command(AddEmail);
Addresses = new ObservableCollection<string>();
Addresses.Add("test1");
Addresses.Add("test2");
}
void Add()
{
AddCommand(string.Empty);
}
}
And here is the view:
<?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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="TestList.MainPage"
x:Name="page">
<StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Addresses"
FontSize="Large"
HorizontalOptions="FillAndExpand"
VerticalOptions="Center"/>
<Button Command="{Binding AddCommand}"
Text="+" FontSize="Title"
VerticalOptions="Center"/>
</StackLayout>
<StackLayout BindableLayout.ItemsSource="{Binding Addresses}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<Entry Text="{Binding ., Mode=TwoWay}" HorizontalOptions="FillAndExpand"/>
<Button Text="-" FontSize="Title""/>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</ContentPage>
I suspect that this is due to the fact that I am working on strings, and as such they cannot be modified in place. Do you have a suggestion on how to solve this problem without introducing a wrapper class or similar?
If you want to change the value of source in code behind by editing the text in Entry .You need to implement the interface INotifyPropertyChanged in class of ObservableCollection .
Define a model class
public class MyModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
string content;
public string Content
{
get
{
return content;
}
set
{
if (content != value)
{
content = value;
OnPropertyChanged("Content");
}
}
}
}
in ViewModel
public ObservableCollection<MyModel> Addresses { get; set; }
Addresses = new ObservableCollection<MyModel>();
Addresses.Add(new MyModel() {Content = "test1" });
Addresses.Add(new MyModel() { Content = "test2" });
in xaml
<Entry Text="{Binding Content, Mode=TwoWay}" HorizontalOptions="FillAndExpand"/>
You really need a wrapper class for this to work, besides if the syntax is too lengthy you can install PropertyCHanged.Fody package
Then all you need to do is add this tag:
[AddINotifyPropertyChangedInterface]
public class MainViewModel
{
public List<Address> Addresses { get; set; }
And in the wrapper class:
[AddINotifyPropertyChangedInterface]
public class Address
{
public string Street { get; set; }
I have a contentpage and a ContentView with the content property bound to the view model
MainPage:
`
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="MvvM.Views.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MvvM.Views"
xmlns:vm="clr-namespace:MvvM.ViewModels">
<!-- ViewModel BindingContext -->
<ContentPage.BindingContext>
<vm:MainViewModel />
</ContentPage.BindingContext>
<Grid>
<Grid.RowDefinitions>
<!-- Header Row -->
<RowDefinition Height="50" />
<!-- ContentView Row -->
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header -->
<Grid Grid.Row="0"
BackgroundColor="CornflowerBlue"
VerticalOptions="FillAndExpand">
<!-- Button On Header -->
<Button Command=""
Text="Page Switch"
VerticalOptions="Center">
<Button.GestureRecognizers>
<TapGestureRecognizer Command="TapGestureCommand" />
</Button.GestureRecognizers>
</Button>
</Grid>
<!-- Content Container -->
<Grid Grid.Row="1" VerticalOptions="Center">
<ContentView Content="{Binding DisplayPage}" />
</Grid>
</Grid>
</ContentPage>
`
ViewModel:
`using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using MvvM.Views;
using Xamarin.Forms;
namespace MvvM.ViewModels
{
public class MainViewModel :INotifyPropertyChanged
{
public MainViewModel()
{
DisplayPage = new Views.MainPage();
}
private ContentPage _displayPage;
public ContentPage DisplayPage
{
get { return _displayPage; }
set
{
if (value != _displayPage)
{
_displayPage = value;
}
}
}
private ContentView _contentToDisplayView;
public ContentView SelectedView
{
get => _contentToDisplayView;
set
{
_contentToDisplayView = value;
OnPropertyChanged();
}
}
public Command TapGestureCommand
{
get
{
return new Command(TapGesture);
}
}
private void TapGesture()
{
_contentToDisplayView = new RedView();
_displayPage.Content = _contentToDisplayView.Content;
OnPropertyChanged();
}
#region PropertyChangedHandler
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}`
and the second page called "RedPage" want to access the content from
`
<?xml version="1.0" encoding="utf-8" ?>
<ContentView x:Class="MvvM.Views.RedView"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:MvvM.ViewModels"
BindingContext="vm:MainViewModel">
<ContentView.Content>
<Grid Width="*"
Height="*"
BackgroundColor="Red" />
</ContentView.Content>
</ContentView> `
The outcome I want is the ContentView content on the RedPage to be displayed in the mainpage contentview.
is creating an instance of the redpage in the view model MVVM complaint ? (I feel that this would tightly bind view to view model?)
how else can i get the content property on red page into the view model ?(cant bind it and sets elements in it as you can only set content property once)
Ideally you would want the ViewModel not to know anything about the View and vice versa, so from that perspective this is not something you would want.
To overcome this, you would want ViewModel-to-ViewModel navigation. So, you just specify to which ViewModel you want to go and the associated View will be loaded. You can implement this manually, and depending on your chosen implementation you would have some way of resolving a View that is linked to that ViewModel.
One way to do this would be by naming conventions and reflection. This means you name all your pages like:
MyPage
YourPage
OurPage
And all the ViewModels like:
MyPageModel
YourPageModel
OurPageModel
Then with reflection you can simply strip off the "Model" suffix and resolve the page from there. Note that I use the Page and PageModel naming, but of course this works for View and ViewModel as well. After you do, you will still have to account for the navigation to and from this views, is it modal or not, etc.
While you can implement all of this manually it would probably be worth while to look into a MVVM framework. The method I just described is how FreshMvvm does this for instance. But there are other good frameworks out there like Prism, Exrin, MvvmCross, etc.