This repository is a playground for exploring various ideas and learning more about Xamarin. It is work in progress and your feedback is much appreciated. The purpose is to provide an overview of various techniques and to evaluate how to best use them.
When learning about Xamarin you will come at some point across techniques such as;
- Attached Properties
- Behaviors
- Effects
- Event Handlers
- Triggers
- Property Triggers
- Data Triggers
- Event Triggers
- Implicit Styles (in conjunction with Triggers)
- Renderer
- Tap Gesture Recognizers
- Bindable Properties [Todo]
- Derived Classes [Todo]
This repo demonstrates the basic structure for each technique and provides a comparison in order to determine when to use best what technique. Checkout the repo. It provides the same example across all of the techniques to make yourself familiar with the differences. An exploration on when to use what technique is provided below, after each technique is introduced. Refer to the excellent Xamarin documentation for details.
Attached properties are static classes with a special type of bindable properties that can be added to any XAML element using the notation class.member
, ex.
<Element MyClass.MyMember="MyValue"/>
The use of attached properties is not limited to only controls, layouts and views but can be used on any C# class that is used in XAML. The implementation of MyClass.MyMember
is ex.
public class MyClass {
public static readonly BindableProperty MyMemberProperty =
BindableProperty.CreateAttached("MyMember", typeof(bool), typeof(MyClass), false, propertyChanged: MyMemberPropertyChanged);
public static bool GetMyMember(BindableObject view) {
return (bool)view.GetValue(CheckFocusProperty);
}
public static void SetMyMember(BindableObject view, bool value) {
view.SetValue(CheckFocusProperty, value);
}
private static void MyMemberPropertyChanged(BindableObject bindable, object oldValue, object newValue) {
....
}
The CreateAttached
method is key. Register your property by a name, its type, a default value and a propertyChanged
delegate that is called when the attribute is used.
Attached properties can be used as wrappers for behaviors and triggers, which make them very powerful. You can Maximize the reuse of an attached property by using it inside an implicit style.
One or more behaviors can be added to class that derives from VisualElement
, ex.
You override the methods OnAttachedTo
and OnDetachingFrom
to tell the behavior what specifically it should do. You need to register an event handler to do anything useful because the methods are only called when a behavior is added or removed from the control.
<Entry>
<Entry.Behaviors>
<local:MyBehavior />
</Entry.Behaviors>
</Entry>
Subclass Behavior and override the two methods to implement a behavior, ex.
public class FocusBehavior : Behavior<Entry> {
protected override void OnAttachedTo(Entry entry) {
....
}
protected override void OnDetachingFrom(Entry entry) {
....
}
}
Other properties i.e. regular bindable properties will be discussed in future.
Effects are similar to behaviors but have a broader application as they can be added classes that derive from Element
. You create an effect by sub classing the RoutingEffect
class in the shared project and the PlatformEffect
class in each platform-specific project overriding the methods OnAttached
, OnDetached
and possibly OnElementPropertyChanged
. Use the effect is XAML like this:
<Entry>
<Entry.Effects>
<local:MyEffect />
</Entry.Effects>
</Entry>
The platform-specific PlatformEffect
s gives access to the native control and container used as well as the Xamarin.Forms view, which makes an effect more powerful than a behavior but requires knowledge of each platform.
The implementation of the shared RoutingEffect
is ex.
public class MyEffect : RoutingEffect {
public MyEffect() : base("com.yourcompany.MyEffect") {}
}
The effect does nothing else but resolves to the platform-specific effect, the basic structure is almost identical on each platform, ex.
[assembly: ResolutionGroupName("com.yourcompany")]
[assembly: ExportEffect(typeof(MyEffect), "MyEffect")]
namespace com.yourcompany {
public class FocusEffect : PlatformEffect {
protected override void OnAttached() {
....
}
protected override void OnDetached() {
....
}
}
}
The attributes ResolutionGroupName
and ExportEffect
are intended uniquely register the effect so that the RoutingEffect
can resolve the platform-specific implementation.
All controls in Xamarin.Forms have the one or the other event. You can use the event handler syntax to register an event hander you provide in the Code Behind, ex.
<Entry Focused="Entry_Focused" />
The implementation of the handler Entry_Focused
is in the Code Behind of the XAML page, ex.
void Entry_Focused(object sender, Xamarin.Forms.FocusEventArgs e) {
....
}
The property trigger is applied to a target type and observes whether one of the properties matches a specific value. Triggers are available on any class deriving from VisualElement
and to the Style
class. If the condition is true one or more setters are applied that can set values for other properties. Use the property trigger like this:
<Entry>
<Entry.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="BackgroundColor" Value="Yellow"/>
</Trigger>
</Entry.Triggers>
</Entry>
No C# code is needed.
The data trigger is applied to a target type and observes the value of a property, referred to as Path
, using data binding. Triggers are available on any class deriving from VisualElement
and to the Style
class. If the condition is true one or more setters are applied that can set values for other properties. The Binding
property requires a Source
, which is set in this use case to the named entry
. Use the data trigger like this:
<Entry x:Name="entry">
<Entry.Triggers>
<DataTrigger TargetType="Entry" Binding="{Binding Source={x:Reference entry},Path=IsFocused}" Value="true">
<Setter Property="BackgroundColor" Value="Yellow"/>
</DataTrigger>
</Entry.Triggers>
</Entry>
No C# code is needed.
The event trigger observes whether an event occurs and registers an action that is executed. Actions derive from the TriggerAction<T>
. Triggers are available on any class deriving from VisualElement
and to the Style
class. Use the event trigger like this:
<Entry>
<Entry.Triggers>
<EventTrigger Event="SomeEvent">
<local:MyTriggerAction />
</EventTrigger>
</Entry>
The action is implemented in C# but sub classing TriggerAction<T>
, ex.
public class MyTriggerAction : TriggerAction<Entry> {
protected override void Invoke(Entry entry) {
....
}
}
All classes that derive from VisualElement
can have a dictionary of resources. Styles are special type of resources that target a particular type. If the Style
has a Key
it must be assigned explicitly to a control, without a Key
it is assigned to all instances of the targeted type. A Style can be defined as a local resource to a control, at the page level or globally. A Style cannot observe changes to properties. Combine it with triggers to support the use case. Use an implicit Style
at the Application
level like this:
<Application xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="mynamespace.App">
<Application.Resources>
<Style TargetType="Entry">
<Style.Triggers>
<Trigger TargetType="Entry" Property="IsFocused" Value="True">
<Setter Property="BackgroundColor" Value="Yellow" />
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
</Application>
No C# code is needed.
A renderer take effects to the next level. Instead of deriving from routing effect and platform effect, which are not control-specific you derive from the renderer class provided by Xamarin.Forms to get full access even replacing the native control used by Xamarin, if you wish. To create your own renderer first derive from a control, ex.
<Entry xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="com.yourcompany.MyEntry"/>
Then provide for each platform a subclass of the respective renderer, ex.
[assembly: ExportRenderer(typeof(MyEntry), typeof(MyEntryRenderer))]
namespace com.yourcompany {
public class MyEntryRenderer : EntryRenderer {
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e) {
base.OnElementChanged(e);
....
}
}
}
The renderer is registered using the attribute ExportRenderer
.
Tap Gesture Recognizer are available for all classes that derive from View
. You can use them to wire them to an event handler or a Command
in your view model (TODO), ex.
<Entry>
<Entry.GestureRecognizers>
<TapGestureRecognizer Tapped="Entry_Focused" NumberOfTapsRequired="1" />
</Entry.GestureRecognizers>
</Entry>
The implementation of the handler Entry_Focused
is in the Code Behind of the XAML page, ex.
void Entry_Focused(object sender, System.EventArgs e) {
....
}
Gesture Recognizers can be considered for scenarios where a particular event is not available or to avoid using the Code Behind and instead to let the view model handle the event.
One question relates to better understand when to use what technique. This section compares various techniques applied to a simple use case
The background color of a view changes each the control gains focus.
Possible evaluation criteria could be:
- Availability of technique. How many controls the technique can be applied to.
- XAML-only. The extent the solution requires C# code or can be purely implemented in XAML
- C# Lines of Code. The number of lines of code needed to solve the problem
- Shared project only. The extent the solution can be implemented in the Forms project or platform-specific projects
- Brevity of XAML. How readable and brief the XAML code is
- Reusability of approach. Can a functionality can be written once and used across all similar controls
The table evaluates the extent each technique contributes to a particular evaluation criteria. The perceived winner is highlighted bold.
Technique | Availability | XAML Only | C# LoC | Shared Project Only | Brevity | Reuse | Comments |
---|---|---|---|---|---|---|---|
Attached Property | 100%+ | No | Medium (39) | Yes | 1 Line | Highest | Any XAML element, Extending View, Wrapping other techniques |
Behavior | 86% | No | Medium (23) | Yes | 3 Lines | Medium | VisualElement.Behaviors , Extending the View |
Effect | 100% | No | High (67) | No | 3 Lines | High | Element.Effects , Access to native control, events and properties |
Event | 100% | No | Low (5) | Yes | 1 Line | Lowest | Events available for a view, Limited to specific view used in the XAML |
Property Trigger | 86% | Yes | 0 | Yes | 8 Lines | High | VisualElement.Triggers and Style.Triggers , UI centric |
Data Trigger | 86% | Yes | 0 | Yes | 8 Lines | High | VisualElement.Triggers and Style.Triggers , UI centric |
Event Trigger | 86% | No | Low (12) | Yes | 8 Lines | High | VisualElement.Triggers and Style.Triggers , Extending View and reuse of event handler |
Implicit Style | 86% | Yes | 0 | Yes | 8 Lines | High | VisualElement.Resources , UI Centric, gobale reuse |
Renderer | 100% | No | High (72) | No | 1 Line | Low | 40 renderer base classes, Full access to the native control used even allowing it to be replaced |
Tap Gesture Recognizer | 72% | No | Low (5) | Yes | 3 Lines | Medium-Low | View.GestureRecognizers , Listen to user interaction and extend behavior. Event handler can be in the Code Behind or a command in the view model |
For the specific use case different options can be considered depending on the principle applied:
- Simple Event handler in C# is easy to understand and very little effort but not very reusable
- Property and data triggers inside Style offer maximum reuse with only XAML. Triggers are however not 100% available.
- Event Trigger or Attached Property as wrapper of a Behaviors inside Attached Property , both inside a Style offer maximum reuse with some C#.
- Effects and Renderer seem to be an overkill for this particular use case.
- The Tap Gesture Recognizer is not suitable in this particular case as tapping on the Entry does not trigger an a tap event.
What do you think?
Credit: This page was written using StackEdit and Markdown Editor