MAUI新生3.5-深入理解XAML:行为Behavior

通过行为Behavior,可以将功能附加到控件上,而不需要在宿主控件上定义,和扩展方法有异曲同功之妙。在MAUI中实现Behavior,有两种方式:①附加行为;②MAUI内置行为。附加行为,通过附加属性方式实现,可以深入理解行为的内在原理;而MAUI的内置行为,封装了实现细节,使用起来非常简洁。

 

 

一、附加行为
附加属性可以在非宿主控件类中使用,同时,它也是可绑定属性,可以定义propertyChanged回调函数,在回调函数中可以调用“控件对象、附加属性的新值和旧值”。当控件的附加属性值发生变化时,propertyChanged回调函数执行业务逻辑,从而实现为控件附加功能。这种方式,我们称之为附加行为。

1、定义一个行为类(附加属性方式)(在Behaviors文件夹下) 

//在Behaviors文件夹下创建立NumberValidateBehavior行为类
public class NumberValidateBehavior
{
    //定义附加属性AttachBehaviorProperty,布尔类型,当值发生改变时propertyChanged,执行OnAttachBehaviorChanged函数
    public static readonly BindableProperty AttachBehaviorProperty =
        BindableProperty.CreateAttached("AttachBehavior",typeof(bool),typeof(NumberValidateBehavior),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);
    }
    //附加属性值发生变化时的回调
    //根据附加属性值(true or false),订阅或移除事件TextChanged的处理程序
    static void OnAttachBehaviorChanged(BindableObject view,object oldValue,object newValue)
    {
        //将宿主对象转化为Entry,使用as,要么成功转为Entry,要么返回null。所以,一般行为都只针对特定控件
        Entry entry = view as Entry;
        if (entry == null)
        {
            return;
        }
        //获取附加属性的新值,并根据新值订阅或移除事件处理程序(事件回调)
        bool attachBehavior = (bool)newValue;
        if (attachBehavior)
        {
            entry.TextChanged += OnEntryTextChanged;
        }
        else
        {
            entry.TextChanged -= OnEntryTextChanged;
        }
    }
    //TextChanged的事件处理程序
    //真正给控件附加的功能,如果输入值可以转换为double类型,则字体颜色为黑色,否则为红色
    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;
    }
}

 

2、使用附加行为

<ContentPage
    ......
    xmlns:behavior="clr-namespace:MauiApp10.Behaviors">

    <VerticalStackLayout>
        <!--将附加属性NumberValidateBehavior.AttachBehavior赋值为true-->
        <Entry Placeholder="请输入数值" behavior:NumberValidateBehavior.AttachBehavior="True"/>
    </VerticalStackLayout>

</ContentPage>

 

3、删除附加行为 

<!--将附加属性值更改为false,执行propertyChanged回调-->
<Entry Placeholder="请输入数值" behavior:NumberValidationBehavior.AttachBehavior="false" />

 

 

二、内置行为
几乎每个MAUI控件,都有一个Behaviors属性,是派生自Behavior类(或Behavior<T>)的对象集合。Behavior类有两个重写方法OnAttachedTo和OnDetachingFrom,可以将附加到控件上的功能写在这两个方法里。当将Behavior对象添加到集合时,执行OnAttachedTo方法,反之将执行OnDetachingFrom。这种方式,我们称之为内置行为。

1、定义一个行为类(派生自Behavior类方式)

//在Behaviors文件夹中创建SimpleNumberValidateBehavior行为类,派生自Behavior<T>,泛型指定为Entry,行为指定应用于Entry
public class SimpleNumberValidateBehavior: Behavior<Entry>
{
    //添加行为时,订阅事件TextChanged的处理程序
    protected override void OnAttachedTo(Entry entry)
    {
        entry.TextChanged += OnEntryTextChanged;
        base.OnAttachedTo(entry);
    }
    //删除行为时,移除事件TextChanged的处理程序
    protected override void OnDetachingFrom(Entry entry)
    {
        entry.TextChanged -= OnEntryTextChanged;
        base.OnDetachingFrom(entry);
    }
    //TextChanged的事件处理程序
    //真正给控件附加的功能,如果输入值可以转换为double类型,则字体颜色为黑色,否则为红色
    private void OnEntryTextChanged(object sender, TextChangedEventArgs args)
    {
        double result;
        bool isValid = double.TryParse(args.NewTextValue, out result);
        ((Entry)sender).TextColor = isValid ? Colors.Black : Colors.Red;
    }
}

 

2、使用内置行为

<ContentPage
    ...
    xmlns:behavior="clr-namespace:MauiApp10.Behaviors">

    <VerticalStackLayout>
        <!--将MAUI内置行为添加到Entry的Behaviors属性集合中-->
        <Entry Placeholder="请输入数值">
            <Entry.Behaviors>
                <behavior:SimpleNumberValidateBehavior/>
            </Entry.Behaviors>
        </Entry>
    </VerticalStackLayout>

</ContentPage>

 


3、删除内置行为:控件的Behaviors属性是一个集合,所以可以在后台代码中移除或清除添加到集合中的行为对象

 

 


三、案例:EventToCommandBehavior

1、MVVM开发模式,我们在ViewModel中定义命令,View的控件绑定命令,如【<Button Text="新增" Command="{Binding AddCommand}"/>】。但是能够直接触发命令的控件及其事件非常有限,如Button仅限于点击事件可以触发命令,而很多控件无法直接绑定命令的。对于XAML来说,事件是一个非常古老和全面的体系,而Command命令,在UI层面的钩子非常少。解决这个问题的思路,就是利用行为Behavior,当控件的某个事件发生时,触发命令

 

2、在CommunityToolkit.MAUI社区工具包有,提供了一个EventToCommandBehavior,它的使用很简单,如下所示。

<!--第一步:安装NuGet包 CommunityToolkit.Maui-->

<!--第二步:使用事件转命令行为-->
<ContentPage  
    ......
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit">
    <Button>
        <Button.Behaviors>
            <toolkit:EventToCommandBehavior
                EventName="Clicked"
                Command="{Binding MyCustomCommand}" />
        </Button.Behaviors>
    </Button>
</ContentPage>

 

3、解读EventToCommandBehavior源码 

public class EventToCommandBehavior : BaseBehavior<VisualElement>
{
    //=======================================================================================================================================
    //定义MethodInfo,方法信息
    readonly MethodInfo eventHandlerMethodInfo = typeof(EventToCommandBehavior).GetTypeInfo()?.GetDeclaredMethod(nameof(OnTriggerHandled)) ?? 
throw new InvalidOperationException($"Cannot find method {nameof(OnTriggerHandled)}"); //定义EventInfo,事件信息 EventInfo? eventInfo; //定义事件处理委托 Delegate? eventHandler;
//可绑定属性=============================================================================================================================== //可绑定属性EventName-事件 public static readonly BindableProperty EventNameProperty = BindableProperty.Create(nameof(EventName), typeof(string), typeof(EventToCommandBehavior), propertyChanged: OnEventNamePropertyChanged); public string? EventName { get => (string?)GetValue(EventNameProperty); set => SetValue(EventNameProperty, value); } //可绑定属性Command-命令 public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(EventToCommandBehavior)); public ICommand? Command { get => (ICommand?)GetValue(CommandProperty); set => SetValue(CommandProperty, value); } //可绑定属性CommandParameter-命令参数 public static readonly BindableProperty CommandParameterProperty = ...... //可绑定属性EventArgs-事件参数 public static readonly BindableProperty EventArgsConverterProperty =...... //内置行为的OnAttachedTo和OnDetachingFrom函数=============================================================================================== //添加行为时,执行RegisterEvent函数,注册事件 protected override void OnAttachedTo(VisualElement bindable) { base.OnAttachedTo(bindable); RegisterEvent(); } //移除行为时,执行UnregisterEvent函数,注销事件 protected override void OnDetachingFrom(VisualElement bindable) { UnregisterEvent(); base.OnDetachingFrom(bindable); } //可绑定属性EventName的值更改时,执行RegisterEvent函数,注册事件 static void OnEventNamePropertyChanged(BindableObject bindable, object oldValue, object newValue) => ((EventToCommandBehavior)bindable).RegisterEvent(); //RegisterEvent和UnregisterEven注册和注销事件逻辑,eventHandler在事件和命令之间建立委托========================================================= void RegisterEvent() { UnregisterEvent(); var eventName = EventName; if (View is null || string.IsNullOrWhiteSpace(eventName)) { return; } //获取eventInfo eventInfo = View.GetType()?.GetRuntimeEvent(eventName) ?? throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't resolve the event.", nameof(EventName)); ArgumentNullException.ThrowIfNull(eventInfo.EventHandlerType); ArgumentNullException.ThrowIfNull(eventHandlerMethodInfo); //******添加一个事件处理方法eventHandler,eventHandlerMethodInfo为触发命令的方法****** eventHandler = eventHandlerMethodInfo.CreateDelegate(eventInfo.EventHandlerType, this) ?? throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't create event handler.", nameof(EventName)); eventInfo.AddEventHandler(View, eventHandler); }
void UnregisterEvent() { //移处事件处理方法enentHandler,并将eventInfo和eventHandler设置为null if (eventInfo is not null && eventHandler is not null) { eventInfo.RemoveEventHandler(View, eventHandler); } eventInfo = null; eventHandler = null; }
//定义虚方法OnTriggerHandled,触发命令======================================================================================================== [Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)] protected virtual void OnTriggerHandled(object? sender = null, object? eventArgs = null) { var parameter = CommandParameter ?? EventArgsConverter?.Convert(eventArgs, typeof(object), null, null); var command = Command; if (command?.CanExecute(parameter) ?? false) { command.Execute(parameter); } } }

 

posted @ 2022-12-08 22:39  functionMC  阅读(588)  评论(0编辑  收藏  举报