广性 发表于 3 天前

Avalonia:辨析 UserControl 与 TemplatedControl

Avalonia:UserControl 与 TemplatedControl

Avalonia 中有两种常见控件创建方式——UserControl(用户控件)和 TemplatedControl(模板控件),两者分别有不同的使用场景和特点。
很多教程不会辨析两者区别。如果初学者(比如之前的我)没有分清楚两者,那会不可避免地写出极其恶心别扭且难以理解的代码。
简单概念辨析

根据Avalonia官方文档

[*]UserControl
UserControl 控件是一种 ContentControl,它代表了一组在预定义布局中可重用的控件。
实际上,UserControl 在 ContentControl 的基础上提供的功能非常有限。不同之处在于,通常不会直接创建
UserControl 类的实例;相反,通常会为应用程序要显示的每个“视图”创建一个 UserControl 类的新子类。
UserControl相比于ContentControl几乎没有添加任何方法或属性。UserControl只是一个可以预定义布局的容器。同时其继承于ContentControl,而ContentControl继承于TemplatedControl。因此UserControl 与 TemplatedControl 实际是继承关系。
Avalonia模板提供的MainView与MainWindow本质上都是UserControl(Window)。
[*]TemplatedControl
模板控件(也称为“Lookless控件”)为在Avalonia中创建自定义控件提供了更高级和可自定义的方法。模板控件将控件的行为和逻辑与其可视外观分离,允许应用程序开发人员通过控件模板进行样式化和模板化。
TemplatedControl 通过后期添加的模板以定义外观布局。类似于TextBox,包括所有在不同主题如Fluent中表现不同的均为模板控件。
[*]Control
为所有控件的基类。Panel,Border等只有后端代码的控件继承于Control。
核心区别


[*]目的

[*]UserControl:面向“组合视图”,把若干现有控件以 XAML + 逻辑组合成一个复用组件,通常用于应用层的具体 UI 单元,而非这里用用那里用用的基础控件。
[*]TemplatedControl:面向“可模板化/样式化控件”,提供公开属性和可替换的视觉模板(ControlTemplate),适合库/控件框架中可主题化的控件。

[*]可定制性

[*]UserControl:不建议外部样式改变内部结构(适合固定结构)。
[*]TemplatedControl:视觉由 Template、Theme、Style 决定,便于主题、样式与模板替换。

[*]生命周期与性能

[*]UserControl 在构建时直接加载 XAML,适合快速组合;大量小的 UserControl 可能导致更深的视觉树。
[*]TemplatedControl 控件模板延迟实例化和更容易优化、主题化。

何时选哪个


[*]选择 UserControl 当:

[*]需要快速组合几个控件形成页面级或区域级 UI,类似于MainView,不关心内部控件的具体实现。
[*]控件结构固定、不需要外部主题化。

[*]选择 TemplatedControl 当:

[*]要构建一个可重用、可主题化且对外提供可绑定属性的控件(例如库控件、按钮、复合但可替换样式的控件)。
[*]希望为不同主题提供不同视觉实现(把样式放在 Theme 中)。

示例

UserControl


[*]MainWindow.axaml
<Window xmlns="https://github.com/avaloniaui"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>xmlns:controls="clr-namespace:ControlDemo.Controls"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>x:
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>Title="ControlDemo">
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><controls:UserControl1 PropertyOne="1" PropertyTwo="2">
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><controls:UserControl1.Template>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ControlTemplate>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><TextBlock Text="1"></TextBlock>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary></ControlTemplate>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary></controls:UserControl1.Template>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary></controls:UserControl1>
</Window>

[*]UserControl1.axaml
<Panel xmlns="https://github.com/avaloniaui"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> xmlns:controls="clr-namespace:ControlDemo.Controls"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> x: x:DataType="controls:UserControl1">
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><StackPanel>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><TextBlock Text="{ Binding PropertyOne,
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>   RelativeSource={ RelativeSource AncestorType={x:Type controls:UserControl1}}}"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>   PointerPressed="PointerPress"/>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><TextBlock Name="Two" Text="{Binding PropertyTwo}"/>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary></StackPanel>
</Panel>

[*]UserControl1.axaml.cs
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ReactiveUI;
using System;
using System.ComponentModel;

namespace ControlDemo.Controls;

public partial class UserControl1 : Border
{

<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>public static readonly DirectProperty<UserControl1, int> PropertyOneProperty = AvaloniaProperty.RegisterDirect<UserControl1, int>(
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>nameof(PropertyOne), o => o.PropertyOne, (o, v) => o.PropertyOne = v);

<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>public int PropertyOne
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>{
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>get => field;
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>set => SetAndRaise(PropertyOneProperty, ref field, value);
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>}

<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>public int PropertyTwo
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>{
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>get;
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>set;
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>} = 1;
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>public UserControl1()
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>{
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>InitializeComponent();
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>PropertyTwo = 0;
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>Two.Background=Brushes.Red;
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>DataContext = this;
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>}
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>private void InitializeComponent(){
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>AvaloniaXamlLoader.Load(this);
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>}
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>private void PointerPress(object? sender, PointerPressedEventArgs e)
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>{
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>Console.WriteLine("Pointer press");
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>}
}注意 以上代码仅供展示用法 是不可运行的 其中糟糕的的写法千万不要学习

[*]控件声明

[*]x:Class 指向后端代码对于的类名,x:DataType 可提供编译时的强类型绑定提示。
[*]x:DataType 代表强类型的 ViewModel,以便使用编译绑定。 建议全部使用 CompiledBinding,使代码便于理解,绑定指向明确
[*]用户控件不需要继承于UserControl,例如示例代码中控件继承于Border,其必要条件是调用AvaloniaXamlLoader.Load(this);,即将axaml中的逻辑树载入视觉树。
[*]AvaloniaXamlLoader.Load(this);不关心axaml文件中的根元素是什么,例如示例代码中的根元素为Panel,而控件实际继承于Border

[*]绑定

[*]相比于模板控件,用户控件不可以直接通过TemplateBinding对控件的属性直接绑定。
[*]推荐使用ViewModel作为DataContext,而非图方便,像示例代码中,将DataContext设为自身,因为这会阻止宿主传入 ViewModel,会破坏外部绑定。。
[*]如果需要绑定到用户控件自身,则可以像示例中使用RelativeSource={ RelativeSource AncestorType={x:Type controls:UserControl1}}}使绑定对象转移到目标用户控件,也可以给用户控件添加Name属性,使用绑定到Name的语法。
[*]相比于模板控件,后端代码对用户控件的绑定更为方便。在axaml中设置Name属性,即可直接在后端代码中直接引用控件对象。
[*]可以直接将事件绑定到后端代码的函数(不可以绑定到委托字段)。

[*]模板

[*]用户控件继承于模板控件,因此用户控件同样也可以模板化,并且会覆盖axaml中定义的内容(但为什么要这么做呢?)

TemplatedControl

TemplatedControl(无外观控件)将行为与视觉彻底分离:控件通过公开的 StyledProperty/DirectProperty 暴露行为和状态,外观由 Template 属性中ControlTemplate 决定。这使得控件可以被主题化、样式化和重用,且模板延迟实例化有利于性能优化。

[*]关键点

[*]暴露属性:使用 AvaloniaProperty.Register / RegisterDirect 注册 StyledProperty 或 DirectProperty,供模板、样式和绑定使用。
[*]模板绑定:在 ControlTemplate 中使用 TemplateBinding 来绑定控件属性到视觉树元素。
[*]获取模板部件:在控件后端重写 OnApplyTemplate(或 OnTemplateApplied),通过 NameScope 找到带有特定 Name(通常以 PART_ 前缀命名)的元素并为其绑定事件或行为。
[*]适用场景:库级控件、可主题化控件、需要高度可定制外观的控件。

[*]TemplatedControl1.axaml.cs
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;

public class TemplatedControl1 : TemplatedControl
{
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>public static readonly StyledProperty<bool> IsCheckedProperty =
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>AvaloniaProperty.Register<TemplatedControl1, bool>(nameof(IsChecked));

<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>public bool IsChecked
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>{
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>get => GetValue(IsCheckedProperty);
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>set => SetValue(IsCheckedProperty, value);
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>}

<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>public override void OnApplyTemplate(TemplateAppliedEventArgs e)
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>{
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>base.OnApplyTemplate(e);

<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>// 获取模板中的命名部件(示例名称:PART_Thumb)
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>var thumb = e.NameScope.Find<Border>("PART_Thumb");
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>if (thumb != null)
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>{
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>thumb.Background=Brushes.Red;
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>}
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>}
}
[*]TemplatedControl1.axaml
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>
[*]MainWindow.axaml
<Window xmlns="https://github.com/avaloniaui"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>xmlns:controls="clr-namespace:ControlDemo.Controls"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>x:
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>Title="ControlDemo">
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><controls:UserControl1 PropertyOne="1" PropertyTwo="2">
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><controls:UserControl1.Template>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ControlTemplate>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><TextBlock Text="1"></TextBlock>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary></ControlTemplate>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary></controls:UserControl1.Template>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary></controls:UserControl1>
</Window><Window xmlns="https://github.com/avaloniaui"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>xmlns:controls="clr-namespace:ControlDemo.Controls"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>x:
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>Title="ControlDemo">
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><controls:UserControl1 PropertyOne="1" PropertyTwo="2">
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><controls:UserControl1.Template>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ControlTemplate>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><TextBlock Text="1"></TextBlock>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary></ControlTemplate>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary></controls:UserControl1.Template>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary></controls:UserControl1>
</Window><Panel xmlns="https://github.com/avaloniaui"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> xmlns:controls="clr-namespace:ControlDemo.Controls"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> x: x:DataType="controls:UserControl1">
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><StackPanel>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><TextBlock Text="{ Binding PropertyOne,
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>   RelativeSource={ RelativeSource AncestorType={x:Type controls:UserControl1}}}"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>   PointerPressed="PointerPress"/>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><TextBlock Name="Two" Text="{Binding PropertyTwo}"/>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary></StackPanel>
</Panel><Panel xmlns="https://github.com/avaloniaui"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> xmlns:controls="clr-namespace:ControlDemo.Controls"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary> x: x:DataType="controls:UserControl1">
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><StackPanel>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><TextBlock Text="{ Binding PropertyOne,
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>   RelativeSource={ RelativeSource AncestorType={x:Type controls:UserControl1}}}"
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary>   PointerPressed="PointerPress"/>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary><TextBlock Name="Two" Text="{Binding PropertyTwo}"/>
<ResourceDictionary xmlns="https://github.com/avaloniaui">
   
</ResourceDictionary></StackPanel>
</Panel>
[*]要点

[*]如果需要对外公开属性、支持 TemplateBinding、允许主题替换,使用 TemplatedControl。
[*]在后端使用 StyledProperty 暴露状态;在模板中使用 TemplateBinding 实现外观与属性联动。
[*]在 OnApplyTemplate 中拾取 PART_* 元素并连接行为或动画,保持视觉与逻辑分离。

[*]TemplateBinding

[*]TemplateBinding 只接受单个属性而不是属性路径,且由于性能原因,TemplateBinding 只支持 OneWay 模式,所以如果你想要使用属性路径进行绑定,你必须使用前文提到的RelativeSource。
[*]TemplateBinding 没有类型转换功能,axaml中绑定的目标必须和属性为同一类型或继承关系
[*]TemplateBinding 只能在 IStyledElement 上使用,例如是错误的

总结


[*]UserControl 与 TemplatedControl 侧重点不同:UserControl 以组合固定视图为主,适合构建页面级或区域级 UI;TemplatedControl 以“无外观”+模板化为主,适合可主题化、可复用的库级控件。
[*]可定制性:UserControl 结构固定、不鼓励外部通过样式改变内部布局;TemplatedControl 通过 Template/Style/Theme 提供高度可替换的视觉表现。
[*]属性与绑定:TemplatedControl 通过 StyledProperty/DirectProperty 暴露可绑定/样式化的状态;UserControl 常用 DataContext / RelativeSource 进行绑定,后端代码引用子元素更直接。
[*]生命周期与性能:UserControl 在加载时直接实例化 XAML;TemplatedControl 的模板延迟实例化,更利于主题和性能优化。
[*]模板与逻辑分离:TemplatedControl 鼓励在 OnApplyTemplate(或 TemplateApplied)中获取 PART_* 元素并绑定行为,保持视觉与逻辑分离;UserControl 更适合把视图与行为写在同一处以提高开发效率。
[*]选择建议:快速组合、结构固定、面向应用层 UI 用 UserControl;需要对外公开属性、可主题化或作为控件库提供复用组件时用 TemplatedControl。
总体原则:按责任选型——页面级组合用 UserControl,控件级可替换外观用 TemplatedControl,兼顾可维护性与可定制性。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: Avalonia:辨析 UserControl 与 TemplatedControl