Behaviors

Browse sample. Browse the sample

.NET Multi-platform App UI (.NET MAUI) behaviors let you add functionality to user interface controls without having to subclass them. Instead, the functionality is implemented in a behavior class and attached to the control as if it was part of the control itself.

Behaviors enable you to implement code that you would normally have to write as code-behind, because it directly interacts with the API of the control in such a way that it can be concisely attached to the control and packaged for reuse across more than one application. They can be used to provide a full range of functionality to controls, such as:

  • Adding an email validator to an Entry.
  • Creating a rating control using a tap gesture recognizer.
  • Controlling an animation.

.NET MAUI supports three different types of behaviors:

Attached behaviors

Attached behaviors are static classes with one or more attached properties. An attached property is a special type of bindable property. They are defined in one class but attached to other objects, and they are recognizable in XAML as attributes that contain a class and a property name separated by a period. For more information about attached properties, see Attached properties.

An attached property can define a propertyChanged delegate that will be executed when the value of the property changes, such as when the property is set on a control. When the propertyChanged delegate executes, it's passed a reference to the control on which it is being attached, and parameters that contain the old and new values for the property. This delegate can be used to add new functionality to the control that the property is attached to by manipulating the reference that is passed in, as follows:

  1. The propertyChanged delegate casts the control reference, which is received as a BindableObject, to the control type that the behavior is designed to enhance.
  2. The propertyChanged delegate modifies properties of the control, calls methods of the control, or registers event handlers for events exposed by the control, to implement the core behavior functionality.

Warning

Attached behaviors are defined in a static class, with static properties and methods. This makes it difficult to create attached behaviors that have state.

Create an attached behavior

An attached behavior can be implemented by creating a static class that contains an attached property that specifies a propertyChanged delegate.

The following example shows the AttachedNumericValidationBehavior class, which highlights the value entered by the user into an Entry control in red if it's not a double:

public static class AttachedNumericValidationBehavior
{
    public static readonly BindableProperty AttachBehaviorProperty =
        BindableProperty.CreateAttached("AttachBehavior", typeof(bool), typeof(AttachedNumericValidationBehavior), 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)
    {
        Entry 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 ? Colors.Black : Colors.Red;
    }
}

In this example, the AttachedNumericValidationBehavior class contains an attached property named AttachBehavior with a static getter and setter, which controls the addition or removal of the behavior to the control to which it will be attached. This attached property registers the OnAttachBehaviorChanged method that will be executed when the value of the property changes. This method registers or de-registers an event handler for the TextChanged event, based on the value of the AttachBehavior attached property. The core functionality of the behavior is provided by the OnEntryTextChanged method, which parses the value entered in the Entry and sets the TextColor property to red if the value isn't a double.

Consume an attached behavior

An attached behavior can be consumed by setting its attached property on the target control.

The following example shows consuming the AttachedNumericValidationBehavior class on an Entry by adding the AttachBehavior attached property to the Entry:


<ContentPage ...
             xmlns:local="clr-namespace:BehaviorsDemos">
    <Entry Placeholder="Enter a System.Double" local:AttachedNumericValidationBehavior.AttachBehavior="true" />
</ContentPage>

The equivalent Entry in C# is shown in the following code example:

Entry entry = new Entry { Placeholder = "Enter a System.Double" };
AttachedNumericValidationBehavior.SetAttachBehavior(entry, true);

The following screenshot shows the attached behavior responding to invalid input:

Screenshot of attached behavior responding to invalid input

Note

Attached behaviors are written for a specific control type (or a superclass that can apply to many controls), and they should only be added to a compatible control.

Remove an attached behavior

The AttachedNumericValidationBehavior class can be removed from a control by setting the AttachBehavior attached property to false:

<Entry Placeholder="Enter a System.Double" local:AttachedNumericValidationBehavior.AttachBehavior="false" />

At runtime, the OnAttachBehaviorChanged method will be executed when the value of the AttachBehavior attached property is set to false. The OnAttachBehaviorChanged method will then de-register the event handler for the TextChanged event, ensuring that the behavior isn't executed as you interact with the control.

.NET MAUI behaviors

.NET MAUI behaviors are created by deriving from the Behavior or Behavior<T> class.

The process for creating a .NET MAUI behavior is as follows:

  1. Create a class that inherits from the Behavior or Behavior<T> class, where T is the type of the control to which the behavior should apply.
  2. Override the OnAttachedTo method to perform any required setup.
  3. Override the OnDetachingFrom method to perform any required cleanup.
  4. Implement the core functionality of the behavior.

This results in the structure shown in the following example:

public class MyBehavior : Behavior<View>
{
    protected override void OnAttachedTo(View bindable)
    {
        base.OnAttachedTo(bindable);
        // Perform setup
    }

    protected override void OnDetachingFrom(View bindable)
    {
        base.OnDetachingFrom(bindable);
        // Perform clean up
    }

    // Behavior implementation
}

The OnAttachedTo method is called immediately after the behavior is attached to a control. This method receives a reference to the control to which it is attached, and can be used to register event handlers or perform other setup that's required to support the behavior functionality. For example, you could subscribe to an event on a control. The behavior functionality would then be implemented in the event handler for the event.

The OnDetachingFrom method is called when the behavior is removed from the control. This method receives a reference to the control to which it is attached, and is used to perform any required cleanup. For example, you could unsubscribe from an event on a control to prevent memory leaks.

The behavior can then be consumed by attaching it to the Behaviors collection of the control.

Create a .NET MAUI Behavior

A .NET MAUI behavior can be implemented by creating a class that derives from the Behavior or Behavior<T> class, and overriding the OnAttachedTo and OnDetachingFrom methods.

The following example shows the NumericValidationBehavior class, which highlights the value entered by the user into an Entry control in red if it's not a double:

public class NumericValidationBehavior : Behavior<Entry>
{
    protected override void OnAttachedTo(Entry entry)
    {
        entry.TextChanged += OnEntryTextChanged;
        base.OnAttachedTo(entry);
    }

    protected override void OnDetachingFrom(Entry entry)
    {
        entry.TextChanged -= OnEntryTextChanged;
        base.OnDetachingFrom(entry);
    }

    void OnEntryTextChanged(object sender, TextChangedEventArgs args)
    {
        double result;
        bool isValid = double.TryParse(args.NewTextValue, out result);
        ((Entry)sender).TextColor = isValid ? Colors.Black : Colors.Red;
    }
}

In this example, the NumericValidationBehavior class derives from the Behavior<T> class, where T is an Entry. The OnAttachedTo method registers an event handler for the TextChanged event, with the OnDetachingFrom method de-registering the TextChanged event to prevent memory leaks. The core functionality of the behavior is provided by the OnEntryTextChanged method, which parses the value entered in the Entry and sets the TextColor property to red if the value isn't a double.

Important

.NET MAUI does not set the BindingContext of a behavior, because behaviors can be shared and applied to multiple controls through styles.

Consume a .NET MAUI behavior

Every .NET MAUI control has a Behaviors collection, to which one or more behaviors can be added:

<Entry Placeholder="Enter a System.Double">
    <Entry.Behaviors>
        <local:NumericValidationBehavior />
    </Entry.Behaviors>
</Entry>

The equivalent Entry in C# is shown in the following code example:

Entry entry = new Entry { Placeholder = "Enter a System.Double" };
entry.Behaviors.Add(new NumericValidationBehavior());

The following screenshot shows the .NET MAUI behavior responding to invalid input:

Screenshot of .NET MAUI behavior responding to invalid input

Warning

.NET MAUI behaviors are written for a specific control type (or a superclass that can apply to many controls), and they should only be added to a compatible control. Attempting to attach a .NET MAUI behavior to an incompatible control will result in an exception being thrown.

Consume a .NET MAUI behavior with a style

.NET MAUI behaviors can be consumed by an explicit or implicit style. However, creating a style that sets the Behaviors property of a control is not possible because the property is read-only. The solution is to add an attached property to the behavior class that controls adding and removing the behavior. The process is as follows:

  1. Add an attached property to the behavior class that will be used to control the addition or removal of the behavior to the control to which the behavior will be attached. Ensure that the attached property registers a propertyChanged delegate that will be executed when the value of the property changes.
  2. Create a static getter and setter for the attached property.
  3. Implement logic in the propertyChanged delegate to add and remove the behavior.

The following example shows the NumericValidationStyleBehavior class, which has an attached property that controls adding and removing the behavior:

public class NumericValidationStyleBehavior : Behavior<Entry>
{
    public static readonly BindableProperty AttachBehaviorProperty =
        BindableProperty.CreateAttached("AttachBehavior", typeof(bool), typeof(NumericValidationStyleBehavior), 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)
    {
        Entry entry = view as Entry;
        if (entry == null)
        {
            return;
        }

        bool attachBehavior = (bool)newValue;
        if (attachBehavior)
        {
            entry.Behaviors.Add(new NumericValidationStyleBehavior());
        }
        else
        {
            Behavior toRemove = entry.Behaviors.FirstOrDefault(b => b is NumericValidationStyleBehavior);
            if (toRemove != null)
            {
                entry.Behaviors.Remove(toRemove);
            }
        }
    }

    protected override void OnAttachedTo(Entry entry)
    {
        entry.TextChanged += OnEntryTextChanged;
        base.OnAttachedTo(entry);
    }

    protected override void OnDetachingFrom(Entry entry)
    {
        entry.TextChanged -= OnEntryTextChanged;
        base.OnDetachingFrom(entry);
    }

    void OnEntryTextChanged(object sender, TextChangedEventArgs args)
    {
        double result;
        bool isValid = double.TryParse(args.NewTextValue, out result);
        ((Entry)sender).TextColor = isValid ? Colors.Black : Colors.Red;
    }
}

In this example, the NumericValidationStyleBehavior class contains an attached property named AttachBehavior with a static getter and setter, which controls the addition or removal of the behavior to the control to which it will be attached. This attached property registers the OnAttachBehaviorChanged method that will be executed when the value of the property changes. This method adds or removes the behavior to the control, based on the value of the AttachBehavior attached property.

The following code example shows an explicit style for the NumericValidationStyleBehavior that uses the AttachBehavior attached property, and which can be applied to Entry controls:

<Style x:Key="NumericValidationStyle" TargetType="Entry">
    <Style.Setters>
        <Setter Property="local:NumericValidationStyleBehavior.AttachBehavior" Value="true" />
    </Style.Setters>
</Style>

The Style can be applied to an Entry by setting its Style property to the style using the StaticResource markup extension:

<Entry Placeholder="Enter a System.Double" Style="{StaticResource NumericValidationStyle}">

For more information about styles, see Styles.

Note

While you can add bindable properties to a behavior that is set or queried in XAML, if you do create behaviors that have state they should not be shared between controls in a Style in a ResourceDictionary.

Remove a .NET MAUI behavior

The OnDetachingFrom method is called when a behavior is removed from a control, and is used to perform any required cleanup such as unsubscribing from an event to prevent a memory leak. However, behaviors are not implicitly removed from controls unless the control's Behaviors collection is modified by the Remove or Clear method:

Behavior toRemove = entry.Behaviors.FirstOrDefault(b => b is NumericValidationStyleBehavior);
if (toRemove != null)
{
    entry.Behaviors.Remove(toRemove);
}

Alternatively, the control's Behaviors collection can be cleared:

entry.Behaviors.Clear();

Note

.NET MAUI behaviors aren't implicitly removed from controls when pages are popped from the navigation stack. Instead, they must be explicitly removed prior to pages going out of scope.

Platform behaviors

Platform behaviors are created by deriving from the PlatformBehavior<TView> or PlatformBehavior<TView,TPlatformView> class. They respond to arbitrary conditions and events on a native control.

A platform behavior can be implemented through conditional compilation, or partial classes. The approach adopted here is to use partial classes, where a platform behavior typically consists of a cross-platform partial class that defines the behaviors API, and a native partial class that implements the behavior on each platform. At build time, multi-targeting combines the partial classes to build the platform behavior on each platform.

The process for creating a platform behavior is as follows:

  1. Create a cross-platform partial class that defines the API for the platform behavior.

  2. Create a native partial class on each platform your app is built for, that has the same name as the cross-platform partial class. This native partial class should inherit from the PlatformBehavior<TView> or PlatformBehavior<TView,TPlatformView> class, where TView is the cross-platform control to which the behavior should apply, and TPlatformView is the native view that implements the cross-platform control on a particular platform.

    Note

    While it's required to create a native partial class on each platform your app is built for, it's not required to implement the platform behavior functionality on every platform. For example, you might create a platform behavior that modifies the border thickness of a native control on some, but not all, platforms.

  3. In each native partial class that you require to implement the platform behavior you should:

    1. Override the OnAttachedTo method to perform any setup.
    2. Override the OnDetachedFrom method to perform any cleanup.
    3. Implement the core functionality of the platform behavior.

The behavior can then be consumed by attaching it to the Behaviors collection of the control.

Create a platform behavior

To create a platform behavior you must first create a cross-platform partial class that defines the API for the platform behavior:

namespace BehaviorsDemos
{
    public partial class TintColorBehavior
    {
        public static readonly BindableProperty TintColorProperty =
            BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(TintColorBehavior));

        public Color TintColor
        {
            get => (Color)GetValue(TintColorProperty);
            set => SetValue(TintColorProperty, value);
        }
    }
}

The platform behavior is a partial class whose implementation will be completed on each required platform with an additional partial class that uses the same name. In this example, the TintColorBehavior class defines a single bindable property, TintColor, that will tint an image with the specified color.

After creating the cross-platform partial class you should create a native partial class on each platform you build the app for. This can be accomplished by adding partial classes to the required child folders of the Platforms folder:

Screenshot of the native partial classes for a platform behavior.

Alternatively you could configure your project to support filename-based multi-targeting, or folder-based multi-targeting, or both. For more information about multi-targeting, see Configure multi-targeting.

The native partial classes should inherit from the PlatformBehavior<TView> class or the PlatformBehavior<TView,TPlatformView> class, where TView is the cross-platform control to which the behavior should apply, and TPlatformView is the native view that implements the cross-platform control on a particular platform. In each native partial class that you require to implement the platform behavior, you should override the OnAttachedTo method and the OnDetachedFrom method, and implement the core functionality of the platform behavior.

The OnAttachedTo method is called immediately after the platform behavior is attached to a cross-platform control. The method receives a reference to the cross-platform control to which it is attached, and optionally a reference to the native control that implements the cross-platform control. The method can be used to register event handlers or perform other setup that's required to support the platform behavior functionality. For example, you could subscribe to an event on a control. The behavior functionality would then be implemented in the event handler for the event.

The OnDetachedFrom method is called when the behavior is removed from the cross-platform control. The method receives a reference to the control to which it is attached, and optionally a reference to the native control that implements the cross-platform control. The method should be used to perform any required cleanup. For example, you could unsubscribe from an event on a control to prevent memory leaks.

Important

The partial classes must reside in the same namespace and use identical names.

The following example shows the TintColorBehavior partial class for Android, which tints an image with a specified color:

using Android.Graphics;
using Android.Widget;
using Microsoft.Maui.Platform;
using Color = Microsoft.Maui.Graphics.Color;

namespace BehaviorsDemos
{
    public partial class TintColorBehavior : PlatformBehavior<Image, ImageView>
    {
        protected override void OnAttachedTo(Image bindable, ImageView platformView)
        {
            base.OnAttachedTo(bindable, platformView);

            if (bindable is null)
                return;
            if (TintColor is null)
                ClearColor(platformView);
            else
                ApplyColor(platformView, TintColor);
        }

        protected override void OnDetachedFrom(Image bindable, ImageView platformView)
        {
            base.OnDetachedFrom(bindable, platformView);

            if (bindable is null)
                return;
            ClearColor(platformView);
        }

        void ApplyColor(ImageView imageView, Color color)
        {
            imageView.SetColorFilter(new PorterDuffColorFilter(color.ToPlatform(), PorterDuff.Mode.SrcIn ?? throw new NullReferenceException()));
        }

        void ClearColor(ImageView imageView)
        {
            imageView.ClearColorFilter();
        }
    }
}

In this example, the TintColorBehavior class derives from the PlatformBehavior<TView,TPlatformView> class, where TView is an Image and TPlatformView is an ImageView. The OnAttachedTo applies the tint color to the image, provided that the TintColor property has a value. The OnDetachedFrom method removes the tint color from the image.

A native partial class must be added on each platform you build your app for. However, you can make the native partial class NO-OP, if the platform behavior isn't required on a specific platform. This can be achieved by providing an empty class:

using Microsoft.UI.Xaml;

namespace BehaviorsDemos
{
    public partial class TintColorBehavior : PlatformBehavior<Image, FrameworkElement>
    {
        // NO-OP on Windows
    }
}

Important

.NET MAUI doesn't set the BindingContext of a platform behavior.

Consume a platform behavior

Every .NET MAUI control has a Behaviors collection, to which one or more platform behaviors can be added:

<Image Source="dotnet_bot.png"
       HeightRequest="200"
       HorizontalOptions="Center">
    <Image.Behaviors>
        <local:TintColorBehavior TintColor="Red" />
    </Image.Behaviors>
</Image>

The equivalent Image in C# is shown in the following example:

Image image = new Image { Source = "dotnet_bot.png", HeightRequest = 200, HorizontalOptions = LayoutOptions.Center };
image.Behaviors.Add(new TintColorBehavior());

The following screenshot shows the platform behavior tinting an image:

Screenshot of .NET MAUI platform behavior tinting an image.

Warning

Platform behaviors are written for a specific control type (or a superclass that can apply to many controls), and they should only be added to a compatible control. Attempting to attach a platform behavior to an incompatible control will result in an exception being thrown.