如本章前两节所述,可绑定属性仅仅定义了控件的数据状态,在UI层面并没有实际意义。要实现一个完整的UI控件,还需要使用控件模板来创建外观样式。如果从Vue或Blazor的组件化来理解自定义控件,逻辑会清晰很多,可绑定属性定义逻辑层的数据,控件模板定义样式层的DOM结构。附加属性的写法比较固定,但控件模板比较灵活,即可以是一个派生自ContentView的独立的XAML类,也可以在资源字典中定义。
一、创建和使用自定义控件(控件模板使用ContentView的派生类)
1、添加>新建项>选择“.NET MAUI”>选择ContentView,自动创建一个XAML文件及其后台代码,如CardView.xaml和CardView.xaml.cs
2、CardView.xaml.cs,后台代码,定义可绑定属性
//部分类,派生自ContentView public partial class CardView : ContentView { //可绑定属性CardTitle public static readonly BindableProperty CardTitleProperty = BindableProperty.Create("CardTitle",typeof(string),typeof(CardView),string.Empty); public string CardTitle { get => (string)GetValue(CardTitleProperty); set => SetValue(CardTitleProperty, value); } //可绑定属性CardContent public static readonly BindableProperty CardContentProperty = BindableProperty.Create("CardContent", typeof(string), typeof(CardView), string.Empty); public string CardContent { get => (string)GetValue(CardContentProperty); set=> SetValue(CardContentProperty, value); } public CardView() { InitializeComponent(); } }
3、CardView.xaml,控件模板
<!--根元素为ContentView类型,x:Class部分类为CardView--> <ContentView x:Class="MauiApp10.Controls.CardView" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Name="this"> <!--绑定上下文为后台代码CardView.xaml.cs--> <!--控件模板使用的排版元素仍然是MAUI内置的元素,本质上并没有创造控件,只是内置控件的一种组合包装--> <!--元素属性可以直接赋值,也可以绑定后台代码的可绑定属性--> <!--直接赋值是硬编码,所有自定义控件实例都一样,而绑定属性才是自定义的部分,每个实例可以赋不一样的值--> <Frame BindingContext="{x:Reference this}"> <Grid> <Label FontSize="32" HorizontalOptions="Center" Text="{Binding CardTitle}" VerticalOptions="Center" /> <Label FontSize="16" HorizontalOptions="Start" Text="{Binding CardContent}" VerticalOptions="Center" /> </Grid> </Frame> </ContentView>
4、在MainPage.xmal页面中,使用自定义控件CardView
<ContentPage ...... xmlns:control="clr-namespace:MauiApp10.Controls"> <!--实例化两个CardView控件--> <VerticalStackLayout> <control:CardView CardContent="标题1" CardTitle="正文1正文1正文1正文1正文1正文1正文1" /> <control:CardView CardContent="标题2" CardTitle="正文2正文2正文2正文2正文2正文2正文2" /> </VerticalStackLayout> </ContentPage>
二、在资源字典中创建和使用控件模板。直接修改MainPage.xmal页面,其它文件不用修改。
1、方式一:TemplateBinding(推荐)
<ContentPage x:Class="MauiApp10.MainPage" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:control="clr-namespace:MauiApp10.Controls"> <!-- 在资源字典中定义一个控制模板ControlTemplate,键名为CardViewNew --> <!-- 为了和在CardView.xaml文件中定义的控件模板区别开来,文字的大小做了更改 --> <!-- 绑定方式使用了TemplateBinding,这是一个语法糖。后面说原始的写法 --> <ContentPage.Resources> <ResourceDictionary> <ControlTemplate x:Key="CardViewNew"> <Frame> <Grid> <Label FontSize="16" HorizontalOptions="Center" Text="{TemplateBinding CardTitle}" VerticalOptions="Center" /> <Label FontSize="10" HorizontalOptions="Start" Text="{TemplateBinding CardContent}" VerticalOptions="Center" /> </Grid> </Frame> </ControlTemplate> </ResourceDictionary> </ContentPage.Resources> <VerticalStackLayout> <!-- 指定ControlTemplate属性,覆盖了在CardView.xaml中定义的控件模板 --> <control:CardView CardContent="标题1" CardTitle="正文1正文1正文1正文1正文1正文1正文1" ControlTemplate="{StaticResource CardViewNew}" /> <!-- 未指定ControlTemplate属性,仍然使用CardView.xaml中定义的控件模板,可以认为CardView.xaml是默认模板 --> <control:CardView CardContent="标题2" CardTitle="正文2正文2正文2正文2正文2正文2正文2" /> </VerticalStackLayout> </ContentPage>
2、方式二:BindingContext
<!-- 设置绑定上下文为【{Binding Source={RelativeSource TemplatedParent}}】 --> <!-- 绑定方式直接使用Binding,而不是TemplateBinding --> <ContentPage.Resources> <ResourceDictionary> <ControlTemplate x:Key="CardViewNew"> <Frame BindingContext="{Binding Source={RelativeSource TemplatedParent}}"> <Grid> <Label FontSize="16" HorizontalOptions="Center" Text="{Binding CardTitle}" VerticalOptions="Center" /> <Label FontSize="10" HorizontalOptions="Start" Text="{Binding CardContent}" VerticalOptions="Center" /> </Grid> </Frame> </ControlTemplate> </ResourceDictionary> </ContentPage.Resources>
3、可以使用样式来隐式应用控件模板,这样自定义控件可以不需要设置ControlTemplate属性
<ContentPage ...... xmlns:control="clr-namespace:MauiApp10.Controls"> <ContentPage.Resources> <ResourceDictionary> <ControlTemplate x:Key="CardViewNew"> <Frame> <Grid> <Label FontSize="16" HorizontalOptions="Center" Text="{TemplateBinding CardTitle}" VerticalOptions="Center" /> <Label FontSize="10" HorizontalOptions="Start" Text="{TemplateBinding CardContent}" VerticalOptions="Center" /> </Grid> </Frame> </ControlTemplate> <!--定义了一个隐式样式,应用到所有CardView控件--> <Style TargetType="control:CardView"> <Setter Property="ControlTemplate" Value="{StaticResource CardViewNew}" /> </Style> </ResourceDictionary> </ContentPage.Resources> <VerticalStackLayout> <!-- 两个自定义控件实例,都隐式应用了资源字典中的控件模板 --> <control:CardView CardContent="标题1" CardTitle="正文1正文1正文1正文1正文1正文1正文1" /> <control:CardView CardContent="标题2" CardTitle="正文2正文2正文2正文2正文2正文2正文2" /> </VerticalStackLayout> </ContentPage>
三、ContentPresenter。类似于Vue的插槽Slot或Blazor的UI片断RenderFragment。不知道怎么翻译,主要实现的功能是,在控件模板中可以用【<ContentPresenter/>】作为占位符,使用自定义控件时,可以在【<自定义控件>{UI}</自定义控件>】位置写UI,这些标签体UI内容,可以替换掉ContentPresenter占位符。以下案例,直接使用资源字典的ControlTemplate,好理解。
<ContentPage ......> <ContentPage.Resources> <ResourceDictionary> <ControlTemplate x:Key="CardViewNew"> <Frame> <Grid RowDefinitions="1*,1*,1*"> <Label FontSize="16" HorizontalOptions="Center" Text="{TemplateBinding CardTitle}" VerticalOptions="Center" /> <Label Grid.Row="1" FontSize="10" HorizontalOptions="Start" Text="{TemplateBinding CardContent}" VerticalOptions="Center" /> <!-- 占位符 --> <ContentPresenter Grid.Row="2" /> </Grid> </Frame> </ControlTemplate> </ResourceDictionary> </ContentPage.Resources> <VerticalStackLayout> <!-- 定义标签体内容UI,插入到模板中 --> <control:CardView CardContent="标题1" CardTitle="正文1" ControlTemplate="{StaticResource CardViewNew}"> <Button Text="这部分UI将插入到控件模板的占位符中" /> </control:CardView> </VerticalStackLayout> </ContentPage>
四、正常情况下,使用自定义控件的页面,是无法获得控件模板中的具体元素的。但通过给控件模板中的元素命名,可以获得控件模板中的具体元素,进而进行操作控制。摘抄文档的例子:
//控件模板,Label命名为changeThemeLabel <ControlTemplate x:Key="TealTemplate"> <Grid> ... <Label x:Name="changeThemeLabel" Grid.Row="2" Text="Change Theme" TextColor="White" HorizontalOptions="Start" VerticalOptions="Center"> </Label> ... </Grid> </ControlTemplate> //在后台代码中,获取Label元素,并修改元素的Text值 public partial class MainPage : ContentPage { Label themeLabel; public MainPage () { InitializeComponent(); } //在OnApplyTemplate方法中获取控件模板的命名元素 protected override void OnApplyTemplate() { base.OnApplyTemplate(); //获取命名元素 themeLabel = (Label)GetTemplateChild("changeThemeLabel"); //修改元素的Text值 themeLabel.Text = OriginalTemplate ? "Aqua Theme" : "Teal Theme"; } }
标签:控件,string,自定义,XAML,CardView,3.3,public,模板 From: https://www.cnblogs.com/functionMC/p/16953889.html