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}" />
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.
I have created bindable property called Text in TargetClass.cs. That Text property is nested bindable property.
TargetClass.cs :
public class TargetClass : BindableObject
{
public static readonly BindableProperty TextProperty =
BindableProperty.Create("Text", typeof(string), typeof(TargetClass), "Default", BindingMode.TwoWay, null,
OnTextChanged);
private static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
}
Then I have created MyView.cs
public class MyView : ContentView
{
private TargetClass target;
Label label;
public TargetClass Target
{
get
{
return target;
}
set
{
target = value;
label.Text = target.Text;
}
}
public MyView()
{
label = new Label();
label.FontSize = 50;
Content = label;
}
}
ViewModel.cs :
public class ViewModel : INotifyPropertyChanged
{
private string m_text = "New Value";
public string TextValue
{
get { return m_text; }
set
{
m_text = value;
OnPropertyChanged("TextValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainPage.xaml :
<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"
xmlns:local="clr-namespace:BindingDemo"
x:Class="BindingDemo.MainPage">
<ContentPage.BindingContext>
<local:ViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<local:TargetClass x:Key="target" Text="{Binding TextValue}"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<local:MyView Target="{StaticResource target}"/>
</StackLayout>
It is working when I give some string value to Text property like,
<ContentPage.BindingContext>
<local:ViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<local:TargetClass x:Key="target" Text="Hello World"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<local:MyView Target="{StaticResource target}"/>
</StackLayout>
in xaml.
But it doesnt work in MVVM binding When I bind the Text property like,
<ContentPage.BindingContext>
<local:ViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<local:TargetClass x:Key="target" Text="{Binding TextValue}"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<local:MyView Target="{StaticResource target}"/>
</StackLayout>
There is no problem with MVVM binding(ViewModel.cs) because it works well with another bindable property.
But it works if the Text property is added in MyView.cs and used like MyView.Text. It doesn't work only when it is added in TargetClass.cs and used like MyView.TargetClass.Text.
Is it possible to use the nested property in data binding in Xamarin Forms?
I don't think objects in the resource dictionary get a BindingContext assigned to them.
So you should modify it from code-behind to assign a context to any resource of type BindableObject
in your page.axml.cs:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (this.Resources != null)
{
foreach (var resource in this.Resources.Values.OfType<BindableObject>())
{
resource.BindingContext = this.BindingContext;
}
}
}
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.
I cant find solution about this orange color? Do i need to write a renderer or can change from resources in Android and IOS?
Yes,if you want to change ListView selecteditem background color, need to use custom render to do this in Xamarin.Forms.
In the PCL, create a class name is ExtendedViewCell which should inherit any ViewCell.
public class ExtendedViewCell : ViewCell
{
public static readonly BindableProperty SelectedBackgroundColorProperty =
BindableProperty.Create("SelectedBackgroundColor",
typeof(Color),
typeof(ExtendedViewCell),
Color.Default);
public Color SelectedBackgroundColor
{
get { return (Color)GetValue(SelectedBackgroundColorProperty); }
set { SetValue(SelectedBackgroundColorProperty, value); }
}
}
In Android project, create a class name as ExtendedViewCellRenderer and make sure to add renderer registration for our ExtendedViewCell class above the namespace.
[assembly: ExportRenderer(typeof(ExtendedViewCell), typeof(ExtendedViewCellRenderer))]
namespace demo3.Droid
{
public class ExtendedViewCellRenderer : ViewCellRenderer
{
private Android.Views.View _cellCore;
private Drawable _unselectedBackground;
private bool _selected;
protected override Android.Views.View GetCellCore(Cell item,
Android.Views.View convertView,
ViewGroup parent,
Context context)
{
_cellCore = base.GetCellCore(item, convertView, parent, context);
_selected = false;
_unselectedBackground = _cellCore.Background;
return _cellCore;
}
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
{
base.OnCellPropertyChanged(sender, args);
if (args.PropertyName == "IsSelected")
{
_selected = !_selected;
if (_selected)
{
var extendedViewCell = sender as ExtendedViewCell;
_cellCore.SetBackgroundColor(extendedViewCell.SelectedBackgroundColor.ToAndroid());
}
else
{
_cellCore.SetBackground(_unselectedBackground);
}
}
}
}
}
Then you can set color for listview SelectedBackgroundColor.
<ListView ItemsSource="{Binding students}">
<ListView.ItemTemplate>
<DataTemplate>
<local:ExtendedViewCell SelectedBackgroundColor="White">
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Username}" TextColor="Yellow" />
<Label Text="{Binding Age}" TextColor="Blue" />
</StackLayout>
</local:ExtendedViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
More detail info and steps in ios platform, you can take a look:
https://blog.wislon.io/posts/2017/04/11/xamforms-listview-selected-colour
Update:
In ios project, create a class name as ExtendedViewCellRenderer and make sure to add renderer registration for our ExtendedViewCell class above the namespace.
[assembly: ExportRenderer(typeof(ExtendedViewCell), typeof(ExtendedViewCellRenderer))]
namespace xamformsdemo.iOS
{
public class ExtendedViewCellRenderer : ViewCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
var cell = base.GetCell(item, reusableCell, tv);
var view = item as ExtendedViewCell;
cell.SelectedBackgroundView = new UIView
{
BackgroundColor = view.SelectedBackgroundColor.ToUIColor(),
};
return cell;
}
}
}
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];
}
}