I understand XAML doesn't really have a facility to extend (inherit) one page from another, but I have seen others use ControlTemplates to achieve this effect.
I am having trouble getting a Binding to work in this scenario. In my example, I have a base ContentPage extended by MainPage. Also, I am using FreshMVVM, so that is why the view model is minimal as FreshMVVM handles all the plumbing of property change notifications.
When the app runs, the label should get the value "Xamarin Forms Header" initialized in the MainPageModel.
I have the fully runnable source on github here, but here's the code. Can someone see what the issue is?
MainPage.xaml
<local:PageBase xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SampleApp"
x:Class="SampleApp.MainPage">
<StackLayout>
</StackLayout>
</local:PageBase>
MainPageModel.xaml
public class MainPageModel : FreshBasePageModel
{
public MainPageModel()
{
LabelText = "Xamarin Forms Header";
}
public string LabelText { get; set; }
}
PageBase.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SampleApp.PageBase">
<ContentPage.ControlTemplate>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Text="{Binding LabelText}" />
<ContentPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</ContentPage.ControlTemplate>
</ContentPage>
you could try to change like this:
MainPage.xaml :
<?xml version="1.0" encoding="utf-8" ?>
<local:PageBase xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SampleApp"
x:Class="SampleApp.MainPage"
LabelText ="{Binding LabelText} "
>
<StackLayout>
</StackLayout>
</local:PageBase>
PageBase.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="SampleApp.PageBase">
<ContentPage.ControlTemplate>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Text="{TemplateBinding LabelText}" />
<ContentPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</ContentPage.ControlTemplate>
</ContentPage>
in your PageBase.xaml.cs:
public partial class PageBase : ContentPage
{
public static readonly BindableProperty LabelTextProperty = BindableProperty.Create("LabelText", typeof(string), typeof(PageBase), "Control Template Demo App");
public string LabelText
{
get { return (string)GetValue(LabelTextProperty); }
}
public PageBase ()
{
InitializeComponent ();
}
}
you could refer to TemplateBinding
TemplateBinding doesn't bind to the View Model so whole code is fundamentally wrong, please read about the topic https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/templates/control-templates/template-binding
Also of note, this would likely work if you used Binding instead of TemplateBinding, but I don't think it makes sense to do that as said something is fundamentally wrong.
Related
Is it possible to add direct content to a CarouselView in Xaml?
I need to convert an old CarouselPage to the newer CarouselView, but it looks like it must be data bound. But my content for each slide is entirely different and does not easily lend itself to an item template.
<?xml version="1.0" encoding="utf-8" ?>
<CarouselPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="myCarousel"
x:Name="carouselPage"
NavigationPage.HasNavigationBar="false" BackgroundColor="#000000">
<ContentPage BackgroundColor="#000000">
<StackLayout BackgroundColor="#000000">
<Label Text="Lorem ipsum" />
<ImageButton Margin="0,20" Source="btn_continue" HorizontalOptions="Center" HeightRequest="30" Clicked="Go_Right" />
</StackLayout>
</ContentPage>
<ContentPage BackgroundColor="#000000">
<StackLayout BackgroundColor="#000000">
<Button Text="X" TextColor="White" BackgroundColor="Transparent" VerticalOptions="Start" HorizontalOptions="End" Clicked="Go_Home"></Button>
<ImageButton Source="slider-2" Aspect="AspectFill" HorizontalOptions="FillAndExpand" VerticalOptions="Start" />
<Grid Margin="0,20" VerticalOptions="Start">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ImageButton Source="btn_back" HorizontalOptions="Center" HeightRequest="30" Grid.Column="0" Clicked="Go_Left" />
<ImageButton Source="btn_close" HorizontalOptions="Center" HeightRequest="30" Grid.Column="1" Clicked="Go_Home" />
</Grid>
</StackLayout>
</ContentPage>
</CarouselPage>
I need to create a multi-slide carousel, where each slide has different content like above. How would I convert that using CarouselViews?
The problem I am having with the CarouselPage is that it does not always behave consistently, which is why I imagine they are slowly deprecating it.
But my content for each slide is entirely different and does not
easily lend itself to an item template.
You need to create different DataTemplate for each slide and Choose item appearance at runtime, for example:
<ContentPage ...
xmlns:controls="clr-namespace:CarouselViewDemos.Controls"
x:Class="CarouselViewDemos.Views.HorizontalLayoutDataTemplateSelectorPage">
<ContentPage.Resources>
<DataTemplate x:Key="StyleATemplate">
...
</DataTemplate>
<DataTemplate x:Key="StyleBTemplate">
...
</DataTemplate>
<controls:ContentSelector x:Key="myContentSelector"
AmericanMonkey="{StaticResource StyleATemplate}"
OtherMonkey="{StaticResource StyleBTemplate}" />
</ContentPage.Resources>
<CarouselView ItemsSource="{Binding Monkeys}"
ItemTemplate="{StaticResource myContentSelector}" />
</ContentPage>
And in code behind, use DataTemplateSelector to select different template for each slide:
public class ContentSelector: DataTemplateSelector
{
public DataTemplate StyleATemplate{ get; set; }
public DataTemplate StyleBTemplate{ get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
if(item.style == A){
return StyleATemplate;
}else{
return StyleBTemplate;
}
}
}
Yes of course you can add different content on every view.
It's necessary to create a CustomViewSelector.
Check my GitHub for a full sample code https://github.com/georgemichailou/ShaXam
Pay attention to these files
https://github.com/georgemichailou/ShaXam/blob/master/ShaXam/UI%20Helper/CustomViewSelector.cs
https://github.com/georgemichailou/ShaXam/blob/master/ShaXam/MainPage.xaml
https://github.com/georgemichailou/ShaXam/blob/master/ShaXam/MainPage.xaml.cs (Line 17-25-27)
Im trying to bind DogImage source which in my Contentview. Im trying to build reusable view objects like frame buttons. I just need to give them Image and Text from outside of contentview. Im using Images from resources folder.
this is my contentView
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PawsApp.Views.AboutMyDogViews.DogsBreedPicker"
BindingContext="{Binding .}">
<ContentView.Content>
<StackLayout x:Name="breedStack" Margin="30,2,30,2">
<Frame HeightRequest="64" CornerRadius="8" BorderColor="White" HasShadow="False" BackgroundColor="White"
VerticalOptions="FillAndExpand" Padding="18,0,18,0">
<Frame.Content>
<StackLayout Orientation="Vertical" VerticalOptions="CenterAndExpand">
<Grid ColumnSpacing="18">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="9*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image x:Name="DogImage" Source="{Binding ImageSourceOf}" Grid.Row="0" Grid.Column="0" ></Image>
<Label x:Name="breedSelector" Text="Breed" FontSize="Medium" TextColor="#5f5d70" Grid.Row="0" Grid.Column="1">
</Label>
</Grid>
</StackLayout>
</Frame.Content>
</Frame>
</StackLayout>
</ContentView.Content>
</ContentView>
And this is CS file
public partial class DogsBreedPicker : ContentView
{
public static readonly BindableProperty ImageSourceProperty =
BindableProperty.Create("ImageSourceOf", typeof(ImageSource), typeof(DogsBreedPicker));
public ImageSource ImageSourceOf
{
get { return GetValue(ImageSourceProperty) as ImageSource; }
set { SetValue(ImageSourceProperty, value);}
}
public DogsBreedPicker()
{
InitializeComponent ();
BindingContext = ImageSourceOf;
}
}
And this is how i want to use it.
<views:DogsBreedPicker ImageSourceOf="dog" TextOf="Breed Selector" x:Name="DogBreed" ></views:DogsBreedPicker>
You are setting your BindingContext to ImageSourceOf in your constructor. Anyway, the properties are not set at this moment, hence ImageSourceOf is still null. However, you do not need to use BindingContext in this case anyway, since you can directly bind to the ImageSourceOf property:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PawsApp.Views.AboutMyDogViews.DogsBreedPicker"
x:Name="View">
<!-- Elided all the other stuff -->
<Image x:Name="DogImage" Source="{Binding ImageSourceOf, Source={x:Reference View}}" Grid.Row="0" Grid.Column="0" />
</ContentView>
remove the assignment of BindingContext from your constructor.
Why does this work?
The default source of all bindings is the BindingContext (of the view, which is propagated to all child views). Anyway, you are by no means restricted to the BindingContext as the source of a binding, but can set the Source of the binding to another object. In this case we are referencing the view (which we have given the name View with x:Name="View") by its name and use it as the source of the binding of the Source of the Image. Since the Path of the binding is ImageSourceOf, the Image.Source will be bound to View.ImageSourceOf.
Content.BindingContext = this;
Solved my issue. But is there any way to make it better? Add comment please.
I have a button
and i want to focus an entry on button click
so my button command is:
public Command FocusPassword
{
get
{
return new Command(() =>
{
Entry myEntry= Application.Current.MainPage.FindByName<Entry>("pEntry");
passwordEntry.Focus();
});
}
}
myEntry always returning null,so how to solve this?
I simply solve this:
Page currentPage = Navigation.NavigationStack.LastOrDefault();
Entry passwordEntry = currentPage.FindByName<Entry>("PassEntry");
passwordEntry.Focus();
Is it myEntry that is null, or passwordEntry? You are assigning the returned object to myEntry but then trying to set the focus on passwordEntry:
Entry myEntry= Application.Current.MainPage.FindByName<Entry>("pEntry");
passwordEntry.Focus();
And yes, make sure that the Entry does have the x:Name set to pEntry, e.g.:
<Entry x:Name="pEntry" ... />
You could try to use my demo as well.
MainPage.xaml (Do not forget to BindingContext)
<?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:EntryDemo"
x:Class="EntryDemo.MainPage">
<StackLayout>
<Grid Margin="10">
<Grid.BindingContext>
<local:BindingViewModel/>
</Grid.BindingContext>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Entry
Grid.Row="0"
HorizontalOptions="CenterAndExpand"
Text="account"
x:Name="accountEntry"
/>
<Entry
Text="password"
Grid.Row="1"
HorizontalOptions="CenterAndExpand"
x:Name="pEntry"
/>
<Button
Grid.Row="2"
HorizontalOptions="CenterAndExpand"
x:Name="MyButton"
Text="FocusEntry "
Command="{Binding FocusCommand}"
/>
</Grid>
</StackLayout>
</ContentPage>
You should run FindByName in execute method
BindingViewModel.cs
public class BindingViewModel: BindableObject
{
public BindingViewModel()
{
FocusCommand = new Command(
execute: () =>
{
Entry accountEntry = Application.Current.MainPage.FindByName<Entry>("accountEntry");
Entry myEntry = Application.Current.MainPage.FindByName<Entry>("pEntry");
accountEntry.Focus();
}
);
}
public ICommand FocusCommand { private set; get; }
}
I use the same XAML many times in my application with two minor differences which are the value of the Text and Selected that I pass in:
<ViewCell Tapped="selectValue" >
<Grid VerticalOptions="CenterAndExpand" Padding="20,0" >
<local:StyledLabel Text="{Binding [1].Name}" HorizontalOptions="StartAndExpand" />
<local:StyledLabel IsVisible="{Binding [1].IsSelected}" TextColor="Gray" HorizontalOptions="End" Text="✓" />
</Grid>
</ViewCell>
Does Xamarin forms have any template feature where I could for example shorten this to something like:
<local:SwitchViewCell Text="{Binding [1].Name}" Selected="{Binding [1].IsSelected}" />
Here's what I have so far:
<?xml version="1.0" encoding="utf-8" ?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
mlns:local="clr-namespace:Japanese;assembly=Japanese"
x:Class="Japanese.SwitchViewCell""
Tapped="selectValue" >
<Grid VerticalOptions="CenterAndExpand" Padding="20,0" >
<local:StyledLabel Text="{Binding Text, Source={x:Reference this}}" HorizontalOptions="StartAndExpand" />
<local:StyledLabel IsVisible="{Binding IsVisible, Source={x:Reference this}}" TextColor="Gray" HorizontalOptions="End" Text="✓" />
</Grid>
</ViewCell>
With this code behind right now:
namespace Japanese.Templates
{
public partial class SwitchViewCell : ViewCell
{
public SwitchViewCell()
{
InitializeComponent();
}
public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(SwitchViewCell));
public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwitchViewCell));
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
public bool IsVisible
{
get
{
return (bool)GetValue(IsVisibleProperty);
}
set
{
SetValue(IsVisibleProperty, value);
}
}
}
}
I'm not sure if this is 100% the way to go but when I try to implement this I get the message:
EventHandler "selectValue" not found in type "Japanese.Templates.SwitchViewCell" (Japanese)
ListView Cell:
For the ListView cell, you can define the ViewCell layout of ListView item.
Ex. PersonCell.xaml
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataTemplates.PersonCell">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.4*" />
</Grid.ColumnDefinitions>
<Label Text="{Binding FirstName}" />
<Label Grid.Column="1" Text="{Binding LastName}" />
<Label Grid.Column="2" Text="{Binding Email}" />
</Grid>
</ViewCell>
Then you can use this into ListView's DataTemplate as:
<ListView ItemSource="{Binding PersonList}">
<ListView.ItemTemplate>
<DataTemplate>
<local:PersonCell />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This way ListView resuses the cell design for each item.
Reusable View:
You can also make a reusable view which can be simply included in your page.
Ex. MyCustomView.xaml:
<Grid xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Xam.Control.MyCustomView">
<StackLayout>
<Label Text="Hello"/>
<Label Text="How Are You?"/>
</StackLayout>
</Grid>
Page:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:customView="clr-namespace:Xam.Control;assembly=Xam"
x:Class="Xam.View.HomePage">
Here, notice that you have to include the namespace and assembly where your custom view resides.
And then in this page, simply add it like:
<customView:MyCustomView />
Sure, just put it in a separate XAML file and define a the bindable properties that you need and map the values onto the controls in your custom control. In fact, the latter is not absolutely needed but is nicer to make it completely reusable.
If you just want to reuse it in your project and always data bind it to the same fields, you can leave it as-is, as the BindingContext will be inherited.
To get you started, you might want to have a look at a blog post of mine about this: https://blog.verslu.is/xamarin/xamarin-forms-xamarin/reusable-custom-usercontrols-with-bindableproperty/
I'm trying to rewrite the sample "SimpleThemeWithTemplateBinding" that uses ControlTemplates, with Prism.
I also added a simple
HeaderText = "changed";
in the button to change the HeaderText in the ControlTemplate, and it works, in the original sample.
So I copied the template in my app.xaml:
<ControlTemplate x:Key="TealTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.1*" />
<RowDefinition Height="0.8*" />
<RowDefinition Height="0.1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.05*" />
<ColumnDefinition Width="0.95*" />
</Grid.ColumnDefinitions>
<BoxView Grid.ColumnSpan="2" Color="Teal" />
<Label Grid.Column="1" Text="{TemplateBinding BindingContext.HeaderText}" TextColor="White" VerticalOptions="Center" />
<ContentPresenter Grid.Row="1" Grid.ColumnSpan="2" />
<BoxView Grid.Row="2" Grid.ColumnSpan="2" Color="Teal" />
<Label Grid.Row="2" Grid.Column="1" Text="(c) Xamarin 2016" TextColor="White" VerticalOptions="Center" />
</Grid>
</ControlTemplate>
and just changed
{TemplateBinding HeaderText}
to
{TemplateBinding BindingContext.HeaderText}
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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="TestAppNSPrism7.Views.PrismContentPage9">
<ContentPage.Content>
<ContentView x:Name="contentView" Padding="0,0,0,0" ControlTemplate="{StaticResource TealTemplate}" >
<StackLayout>
<Label Text="Welcome to Xamarin.Forms! page1"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
<Label Text="Buazz"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
<Button Command="{Binding ButtonChangeValueCommand}" Text="Change value" ></Button>
</StackLayout>
</ContentView>
</ContentPage.Content>
</ContentPage>
VIEWMODEL:
public class PrismContentPage9ViewModel : ViewModelBase
{
ControlTemplate tealTemplate;
private string _headerText = "test";
public string HeaderText
{
get
{
return _headerText;
}
set { SetProperty(ref _headerText, value); }
}
public PrismContentPage9ViewModel(INavigationService navigationService) : base(navigationService)
{
tealTemplate = (ControlTemplate)Application.Current.Resources["TealTemplate"];
}
private DelegateCommand _buttonChangeValueCommand;
public DelegateCommand ButtonChangeValueCommand =>
_buttonChangeValueCommand ?? (_buttonChangeValueCommand = new DelegateCommand(ExecuteButtonChangeValueCommand));
void ExecuteButtonChangeValueCommand()
{
HeaderText = "changed";
}
}
The Page gets loaded correctly, with the ControlTemplate, and the HeaderText is "test".
So it seems the HeaderText binding with the ControlTemplate is working.
But when I set the HeaderText to "changed", the Label doesn't get updated.
I debugged and checked that once I press the button it goes through ExecuteButtonChangeValueCommand() and SetProperty(ref _headerText, value)
Any suggestion?
Thanks!
I changed the TemplateBinding from :
Text="{TemplateBinding BindingContext.HeaderText}"
to:
Text="{TemplateBinding Parent.BindingContext.HeaderText}"
and it updates now when I press your changed button.
I believe its due to the template not having a binding context automatically set but the template's parent (PrismContentPage9) has its BindingContext auto-wired from Prism's AutowireViewModel property, e.g.,
prism:ViewModelLocator.AutowireViewModel="True"
Let me know if that works for you.