Microsoft documentation shows how to inherit from a ControlTemplate and use a ContentPresenter.
It shows how to use string properties to populate string bound items in the template. (e.g. HeaderText)
It doesn't show how to do the same with commands. I want to drive the command behavior of a button in the template via the implementing contentpage/viewmodel.
Following the property example, I tried the same with an ICommand but it gets ignored. Meaning, the button isn't executing the provided command. Is commanding not supported?
Example
This is in my ControlTemplate, called ApplicationChrome.xaml
<Label Grid.Row="0"
Margin="20,0,0,0"
Text="{TemplateBinding HeaderText}"
TextColor="White"
FontSize="Title"
VerticalOptions="Center"/>
<Button Grid.Column="0"
x:Name="LeftButton"
Margin="20,0,0,0"
Text="Change Label"
TextColor="White"
HorizontalOptions="Start"
VerticalOptions="Center"
Command="{TemplateBinding LeftButtonTemplateCommand}"
The code-behind defines both Bindable Properties
public static readonly BindableProperty HeaderTextProperty = BindableProperty.Create("HeaderText", typeof(string), typeof(ContentPage), null, BindingMode.TwoWay);
public string HeaderText
{
get => (string)GetValue(HeaderTextProperty);
set => SetValue(HeaderTextProperty, value);
}
public static readonly BindableProperty LeftButtonTemplateCommandProperty = BindableProperty.Create("LeftButtonCommand", typeof(ICommand), typeof(ApplicationChrome), null);
public ICommand LeftButtonTemplateCommand
{
get => (ICommand) GetValue(LeftButtonTemplateCommandProperty);
set => SetValue(LeftButtonTemplateCommandProperty, value);
}
My implementing view sets both Bindables
<core:ApplicationChrome 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"
xmlns:core="clr-namespace:FEOD.Core;assembly=FEOD"
mc:Ignorable="d"
HeaderText="FE | Home"
LeftButtonTemplateCommand="{Binding LeftButtonCommand}"
x:Class="FEOD.Views.HomeView">
The implementing view's BindingContext is set to it's viewmodel which defines the LeftButtonCommand
public ICommand LeftButtonCommand { get; private set; }
private static void OnLeftButtonClicked(object obj)
{
var a = 1;
}
public HomeViewModel()
{
LeftButtonCommand = new Command(OnLeftButtonClicked);
}
The bound HeaderText displays "FE | Home" just fine. But the bound command never fires OnLeftButtonClicked.
The first parameter of BindableProperty.Create() method has to be "LeftButtonTemplateCommand" not "LeftButtonCommand". The Property name has to exactly match for Binding to work.
Related
I have a xaml page that contains two instances of the same content view. The content view have a datepicker which should update a value in the parent view model ( each content view should update a different variable in the view model). I tried to do the bindiable property but it's not working. I set the BindingMode to TwoWay but that's not working.
The issue is that the binding is not working from the contentview to the parent viewmodel through the bindiable property. Any input is much appreciated.
Below is my code:
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
BackgroundColor="{DynamicResource PageBackgroundColor}"
xmlns:picker="clr-namespace:TestSync.View"
xmlns:viewmodel="clr-namespace:TestSync.ViewModel"
x:DataType="viewmodel:TimeTrackerViewModel"
x:Class="TestSync.MainPage">
<VerticalStackLayout>
<Label Text="{Binding SelectedDate}"/>
<Label Text="{Binding SelectedDate1}"/>
<picker:DateTimePickerContentView CardTitle="First DatePicker" CardDate="{Binding SelectedDate,Mode=TwoWay}" />
<picker:DateTimePickerContentView CardTitle="Second DatePicker" CardDate="{Binding SelectedDate1,Mode=TwoWay}" />
</VerticalStackLayout>
</ContentPage>
TimeTrackerViewModel.cs
namespace TestSync.ViewModel
{
public partial class TimeTrackerViewModel :ObservableObject
{
[ObservableProperty]
public DateTime selectedDate;
[ObservableProperty]
public DateTime selectedDate1;
}
}
DateTimePickerContentView.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodel="clr-namespace:TestSync.View"
x:DataType="viewmodel:DateTimePickerContentView"
x:Class="TestSync.View.DateTimePickerContentView"
>
<VerticalStackLayout>
<Label Text="{Binding CardTitle}"/>
<DatePicker x:Name="myDate" Date="{Binding CardDate}" />
</VerticalStackLayout>
</ContentView>
and DateTimePickerContetntView.xaml.cs
namespace TestSync.View;
public partial class DateTimePickerContentView : ContentView
{
public static readonly BindableProperty CardTitleProperty = BindableProperty.Create(nameof(CardTitle), typeof(string), typeof(DateTimePickerContentView), string.Empty);
public string CardTitle
{
get => (string)GetValue(DateTimePickerContentView.CardTitleProperty);
set => SetValue(DateTimePickerContentView.CardTitleProperty, value);
}
public static readonly BindableProperty CardDateProperty = BindableProperty.Create(nameof(CardDate), typeof(DateTime), typeof(DateTimePickerContentView), defaultValue:DateTime.Parse("12/15/1992"),defaultBindingMode:BindingMode.TwoWay,propertyChanged:test);
private static void test(BindableObject bindable, object oldValue, object newValue)
{
var mytest= bindable as DateTimePickerContentView;
mytest.myDate.Date = (DateTime)newValue;
}
public DateTime CardDate
{
get => (DateTime)GetValue(DateTimePickerContentView.CardDateProperty);
set => SetValue(DateTimePickerContentView.CardDateProperty, value);
}
public DateTimePickerContentView()
{
InitializeComponent();
BindingContext = this;
}
}
I give you a workaround here.
For DateTimePickerContentView.xaml, define the BindingContext
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
...
x:Name="this">
<VerticalStackLayout BindingContext="{x:Reference this}">
<Label Text="{Binding CardTitle}"/>
<DatePicker x:Name="myDate" Date="{Binding CardDate}" />
</VerticalStackLayout>
</ContentView>
So for DateTimePickerContentView.cs, just delete this line
...
public DateTimePickerContentView()
{
InitializeComponent();
//BindingContext = this;
}
For data binding in a ContentView, you could refer to this official doc: Define the UI.
And if you want to set a default value, you should set it in TimeTrackerViewModel, because TimeTrackerViewModel's constructor execute after custom control set the default value. Then it will be replaced such as 1/1/1900 .
public TimeTrackerViewModel()
{
SelectedDate = DateTime.Parse("12/15/1992");
SelectedDate1 = DateTime.Parse("12/15/1992");
}
Hope it works for you.
<Ctrl:CtrlPickerButton
x:Name="xEditSpeed"
Grid.Row="5"
Grid.Column="1"
Padding="0,10,0,10"
SelectItem="{Binding Speed}" />
*************** CtrlPickerButton.xaml ****************
<ContentView
x:Class="POP.Controls.CtrlPickerButton"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Ctrl="clr-namespace:POP.Controls"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<ContentView.Content>
<Frame
Padding="1"
BackgroundColor="{StaticResource ColorBorder000}"
CornerRadius="0"
HasShadow="False"
HorizontalOptions="FillAndExpand">
<Frame
Padding="0"
CornerRadius="0"
HasShadow="false">
<Ctrl:PickerCommon
x:Name="xPicker"
Margin="5,0,0,0"
HorizontalOptions="FillAndExpand"
TextColor="{DynamicResource ColorText000}"
VerticalOptions="FillAndExpand" />
</Frame>
</Frame>
</ContentView.Content>
</ContentView>
*************** CtrlPickerButton.cs ****************
public partial class CtrlPickerButton : ContentView
{
public CtrlPickerButton()
{
InitializeComponent();
}
public int SelectItem
{
get
{
return (int)GetValue(ItemChangedProperty);
}
set
{
SetValue(ItemChangedProperty, value);
xPicker.SelectedIndex = value;
}
}
public static readonly BindableProperty ItemChangedProperty = BindableProperty.Create(
propertyName: nameof(SelectItem),
returnType: typeof(int),
declaringType: typeof(CtrlPickerButton),
defaultValue: 1,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: SelectedItemProPertyChanged);
private static void SelectedItemProPertyChanged(BindableObject bindable, object oldValue, object newValue)
{
(bindable as CtrlPickerButton).SelectItem = (int)newValue;
}
}
The binding property naming convention was followed.
But I get a Missmatching error.
What went wrong?
So the thing is that there is a Naming convention that you need to follow when you create a Bindable property.
Your Property has a name that follows CamelCase. Eg below:
public int SelectedItem { get; set; }
Create your Bindable part for your property:
public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create(
propertyName: nameof(SelectedItem),
returnType: typeof(int),
declaringType: typeof(CtrlPickerButton),
defaultValue: 1,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: SelectedItemProPertyChanged);
Focus on how the name of your BindableProperty's name is your PropertyName+ the word Property so "SelectedItemProperty", Now the first value that you need to pass in your Create method is the name of your property i.e. "SelectedItem"
Now your property's getter and setter change to update based on our Bindable property:
public int SelectedItem
{
get =>(int)GetValue(SelectedItemProperty);
set => SetValue(SelectedItemProperty, value);
}
More detailed information here: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/bindable-properties#create-a-bindable-property
Hope this helps
Good luck!
I have created a custom control,which is a ContentView with a Label and an Entry
The xaml of the custom controls looks like this:
<Label Text="{Binding Source={x:Reference ValidationControl}, Path=Caption}"/>
<Entry Text="{Binding Source={x:Reference ValidationControl}, Path=Value, Mode=TwoWay}" />
The code behind of the custom control looks like this:
public static readonly BindableProperty CaptionProperty = BindableProperty.Create(
nameof(Caption), typeof(string), typeof(ValidationEntry), default(string));
public string Caption
{
get => (string)GetValue(CaptionProperty);
set => SetValue(CaptionProperty, value);
}
public static readonly BindableProperty ValueProperty = BindableProperty.Create(
nameof(Value), typeof(string), typeof(ValidationEntry), default(string));
public string Value
{
get => (string)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
I’m using the custom control in the following way
<controls:ValidationEntry Caption=”Name:” Value="{Binding FullName, Mode=TwoWay}" />
My question is how to add behaviors to the custom control?
I would like to add them in the place that I’m using the control. i.e.
<controls:ValidationEntry Caption="Name:"
Value="{Binding FullName, Mode=TwoWay}">
<controls:ValidationEntry.EntryBehaviors>
<behaviors:EntryLengthValidatorBehavior IgnoreSpaces="True"/>
</controls:ValidationEntry.EntryBehaviors>
</controls:ValidationEntry>
You can create a behaviors directly, I add a NumericValidationBehavior in my custom entry to check the data if it is double.If type of the data is not double, the color of text will be set to red.
Here is xaml code.
<StackLayout>
<local:MyEntry local:NumericValidationBehavior.AttachBehavior="true">
</local:MyEntry>
</StackLayout>
Here is NumericValidationBehavior.cs
public static class NumericValidationBehavior
{
public static readonly BindableProperty AttachBehaviorProperty =
BindableProperty.CreateAttached(
"AttachBehavior",
typeof(bool),
typeof(NumericValidationBehavior),
false,
propertyChanged: OnAttachBehaviorChanged);
public static bool GetAttachBehavior(BindableObject view)
{
return (bool)view.GetValue(AttachBehaviorProperty);
}
public static void SetAttachBehavior(BindableObject view, bool value)
{
view.SetValue(AttachBehaviorProperty, value);
}
static void OnAttachBehaviorChanged(BindableObject view, object oldValue, object newValue)
{
var entry = view as Entry;
if (entry == null)
{
return;
}
bool attachBehavior = (bool)newValue;
if (attachBehavior)
{
entry.TextChanged += OnEntryTextChanged;
}
else
{
entry.TextChanged -= OnEntryTextChanged;
}
}
static void OnEntryTextChanged(object sender, TextChangedEventArgs args)
{
double result;
bool isValid = double.TryParse(args.NewTextValue, out result);
((Entry)sender).TextColor = isValid ? Color.Default : Color.Red;
}
}
Update
I create a custom view with ContentView
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="BeHavDemo.MyView">
<ContentView.Content>
<StackLayout>
<Label Text="xxxx"/>
<Entry Text="eeeee" />
</StackLayout>
</ContentView.Content>
</ContentView>
Then I create a behavior.
public class MyBeha : Behavior<MyView>
{
protected override void OnAttachedTo(BindableObject view)
{
base.OnAttachedTo(view);
var myview=view as MyView;
StackLayout stackLayout = (StackLayout)myview.Content;
Label label = (Label)stackLayout.Children[0];
Entry entry=(Entry) stackLayout.Children[1];
}
}
I would like to adjust the default controltemplate of a Button. In WPF i would just use blend to see the default controltemplate but for Xamarin.Forms i can not use blend.
Also in the App.xaml file i see a reference to
<ResourceDictionary Source="Resource Dictionaries/StandardStyles.xaml"/>
but i do not find the StandardStyles.xaml file so i am out of luck there as well.
And on the Xamarin site i do not find the default controltemplates neither.
So where/how can i find the default controltemplates for the Xamarin.Forms controls?
At this point, Xamarin.Forms doesn't provide templating support for buttons - so there is no default template(s) to refer (as we do in WPF) or ControlTemplate property in Button. It simply renders the platform version of button in target platform.
Control templates usually are supported by controls that have a Content property. A good example would be ContentView. You can write a custom button control while extending a ContentView, while providing templating support, and use ContentPresenter to render the associated button.
EDIT 1 - Custom button control with templated support
For example:
public class TemplatedButton : ContentView
{
public TemplatedButton()
{
var button = new Button();
button.SetBinding(Button.TextColorProperty, new Binding(nameof(TextColor), source: this));
button.SetBinding(BackgroundColorProperty, new Binding(nameof(BackgroundColor), source: this));
button.SetBinding(IsEnabledProperty, new Binding(nameof(IsEnabled), source: this));
button.SetBinding(Button.TextProperty, new Binding(nameof(Text), source: this));
button.SetBinding(Button.CommandProperty, new Binding(nameof(Command), source: this));
button.SetBinding(Button.CommandParameterProperty, new Binding(nameof(CommandParameter), source: this));
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.SetBinding(TapGestureRecognizer.CommandProperty, new Binding(nameof(Command), source: this));
tapGestureRecognizer.SetBinding(TapGestureRecognizer.CommandParameterProperty, new Binding(nameof(CommandParameter), source: this));
GestureRecognizers.Add(tapGestureRecognizer);
Content = button;
}
public static readonly BindableProperty TextProperty =
BindableProperty.Create(
"Text", typeof(string), typeof(TemplatedButton),
defaultValue: default(string));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly BindableProperty CommandProperty =
BindableProperty.Create(
"Command", typeof(ICommand), typeof(TemplatedButton),
defaultValue: new Command((obj) => System.Diagnostics.Debug.WriteLine("TemplatedButton Tapped")));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create(
"CommandParameter", typeof(object), typeof(TemplatedButton),
defaultValue: default(object));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly BindableProperty TextColorProperty =
BindableProperty.Create(
"TextColor", typeof(Color), typeof(TemplatedButton),
defaultValue: default(Color));
public Color TextColor
{
get { return (Color)GetValue(TextColorProperty); }
set { SetValue(TextColorProperty, value); }
}
}
Sample usage:
<!-- App/Page resources -->
<ResourceDictionary>
<ControlTemplate x:Key="ThreeBorderBtn">
<Grid RowSpacing="0" ColumnSpacing="0" Margin="0">
<BoxView Margin="5" BackgroundColor="Purple" />
<BoxView Margin="10" BackgroundColor="Green" />
<BoxView Margin="15" BackgroundColor="Red" />
<ContentPresenter Margin="20" />
</Grid>
</ControlTemplate>
</ResourceDictionary>
<!-- Control usage -->
<!-- make sure to register xmlns:local namespace -->
<local:TemplatedButton
HeightRequest="100"
Text="Button Caption!"
TextColor="Teal"
Command="{Binding ClickCommand}"
BackgroundColor="White"
ControlTemplate="{StaticResource ThreeBorderBtn}" />
I recently started building a Xamarin Forms application using Prism.
I'm not able to navigate with the MasterDetail Navigation. The button I use to navigate seems to not make the binding correctly. I never been able to reach the executed command with a breakpoint when clicking on the button.
Everything except the command binding seems to do the binding correctly, so I really have no idea on what is going on.
I already checked out the GitHub sample made available by the Prism team (HamburgerMenu project). I'm convince to use the exact same configuration as the sample but no way to make it works on my project.
Bellow is the code used currently:
MainPage.xaml
<MasterDetailPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MonkeyVault.Views.MainPage">
<MasterDetailPage.Master>
<NavigationPage Title="Required Foo" Icon="ic_menu.png">
<x:Arguments>
<ContentPage Title="Menu">
<StackLayout Padding="40">
<Label Text="{Binding UserName, StringFormat='Hello, {0}'}"/>
<Button Text="Sites" Command="{Binding NavigateCommand}" CommandParameter="Navigation/Sites" />
</StackLayout>
</ContentPage>
</x:Arguments>
</NavigationPage>
</MasterDetailPage.Master>
</MasterDetailPage>
MainPageViewModel.cs
public class MainPageViewModel : BaseViewModel
{
#region Fields
private string _userName;
#endregion
#region Properties
public string UserName
{
get => _userName;
set => SetProperty(ref _userName, value);
}
public DelegateCommand<string> NavigateCommand;
public DelegateCommand NCommand;
#endregion
public MainPageViewModel(INavigationService navigationService)
: base(navigationService)
{
Title = "Main Page";
NavigateCommand = new DelegateCommand<string>(OnNavigateCommandExecuted);
}
private async void OnNavigateCommandExecuted(string path)
{
await _navigationService.NavigateAsync(path);
}
}
If someone has already encountered this problem or has any idea I would be greatful.
You need to create your DelegateCommand as a Property.
public DelegateCommand<string> NavigateCommand { get; set; }
Admittedly I am just guessing here, but I have had problems binding to Fields before and needed to change it to a property to get binding.