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); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)