zl程序教程

您现在的位置是:首页 >  其他

当前栏目

MAUI新生3.3-深入理解XAML:控件模板ControlTemplate

2023-04-18 15:25:02 时间

如本章前两节所述,可绑定属性仅仅定义了控件的数据状态,在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文件中,定义UI样式

<!--根元素为ContentView类型,x:Class部分类为CardView,x:Name将控件类命名为this-->
<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">
    <!--BindingContext设置为this,即控件的实例对象,这样可以绑定后台代码定义的可绑定属性-->
    <!--控件模板使用的排版元素仍然是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";
    }
}