news 2026/4/9 2:26:57

Avalonia:辨析 UserControl 与 TemplatedControl

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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
/* by 01022.hk - online tools website : 01022.hk/zh/calclength.html */ <Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="clr-namespace:ControlDemo.Controls" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="ControlDemo.MainWindow" Title="ControlDemo"> <controls:UserControl1 PropertyOne="1" PropertyTwo="2"> <controls:UserControl1.Template> <ControlTemplate> <TextBlock Text="1"></TextBlock> </ControlTemplate> </controls:UserControl1.Template> </controls:UserControl1> </Window>
  • UserControl1.axaml
/* by 01022.hk - online tools website : 01022.hk/zh/calclength.html */ <Panel xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="clr-namespace:ControlDemo.Controls" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="ControlDemo.Controls.UserControl1" x:DataType="controls:UserControl1"> <StackPanel> <TextBlock Text="{ Binding PropertyOne, RelativeSource={ RelativeSource AncestorType={x:Type controls:UserControl1}}}" PointerPressed="PointerPress"/> <TextBlock Name="Two" Text="{Binding PropertyTwo}"/> </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 { public static readonly DirectProperty<UserControl1, int> PropertyOneProperty = AvaloniaProperty.RegisterDirect<UserControl1, int>( nameof(PropertyOne), o => o.PropertyOne, (o, v) => o.PropertyOne = v); public int PropertyOne { get => field; set => SetAndRaise(PropertyOneProperty, ref field, value); } public int PropertyTwo { get; set; } = 1; public UserControl1() { InitializeComponent(); PropertyTwo = 0; Two.Background=Brushes.Red; DataContext = this; } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } private void PointerPress(object? sender, PointerPressedEventArgs e) { Console.WriteLine("Pointer press"); } }

注意 以上代码仅供展示用法 是不可运行的 其中糟糕的的写法千万不要学习

  1. 控件声明
    • x:Class 指向后端代码对于的类名,x:DataType 可提供编译时的强类型绑定提示。
    • x:DataType 代表强类型的 ViewModel,以便使用编译绑定。 建议全部使用 CompiledBinding,使代码便于理解,绑定指向明确
    • 用户控件不需要继承于UserControl,例如示例代码中控件继承于Border,其必要条件是调用AvaloniaXamlLoader.Load(this);,即将axaml中的逻辑树载入视觉树。
    • AvaloniaXamlLoader.Load(this);不关心axaml文件中的根元素是什么,例如示例代码中的根元素为Panel,而控件实际继承于Border
  2. 绑定
    • 相比于模板控件,用户控件不可以直接通过TemplateBinding对控件的属性直接绑定。
    • 推荐使用ViewModel作为DataContext,而非图方便,像示例代码中,将DataContext设为自身,因为这会阻止宿主传入 ViewModel,会破坏外部绑定。。
    • 如果需要绑定到用户控件自身,则可以像示例中使用RelativeSource={ RelativeSource AncestorType={x:Type controls:UserControl1}}}使绑定对象转移到目标用户控件,也可以给用户控件添加Name属性,使用绑定到Name的语法。
    • 相比于模板控件,后端代码对用户控件的绑定更为方便。在axaml中设置Name属性,即可直接在后端代码中直接引用控件对象。
    • 可以直接将事件绑定到后端代码的函数(不可以绑定到委托字段)。
  3. 模板
    • 用户控件继承于模板控件,因此用户控件同样也可以模板化,并且会覆盖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 { public static readonly StyledProperty<bool> IsCheckedProperty = AvaloniaProperty.Register<TemplatedControl1, bool>(nameof(IsChecked)); public bool IsChecked { get => GetValue(IsCheckedProperty); set => SetValue(IsCheckedProperty, value); } public override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); // 获取模板中的命名部件(示例名称:PART_Thumb) var thumb = e.NameScope.Find<Border>("PART_Thumb"); if (thumb != null) { thumb.Background=Brushes.Red; } } }
  • TemplatedControl1.axaml

    <ResourceDictionary xmlns="https://github.com/avaloniaui"> <Style Selector="TemplatedControl1"> <Setter Property="Template"> <ControlTemplate> <Border Background="{TemplateBinding Background}" Padding="6" CornerRadius="4"> <Grid> <Border Name="PART_Track" Background="{TemplateBinding Foreground}" CornerRadius="2"/> <Border Name="PART_Thumb" Width="16" Height="16" Background="White" HorizontalAlignment="Left"/> </Grid> </Border> </ControlTemplate> </Setter> </Style> </ResourceDictionary>
  • MainWindow.axaml

    <Window xmlns="https://github.com/avaloniaui" xmlns:local="clr-namespace:ControlDemo.Controls"> <StackPanel> <local:TemplatedControl1 IsChecked="{Binding SomeBool}" /> <local:TemplatedControl1 IsChecked="{Binding SomeBool}"> <local:TemplatedControl1.Template> <ControlTemplate> <Border Background="{TemplateBinding Background}" Padding="6" CornerRadius="4"> <Grid> <Border Name="PART_Thumb" Width="16" Height="16" Background="White" HorizontalAlignment="Left"/> </Grid> </Border> </ControlTemplate> </local:TemplatedControl1.Template> </local:TemplatedControl1> </StackPanel> </Window>
  • 要点

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

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

总结

  • 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,兼顾可维护性与可定制性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/28 18:29:17

git的常用命令的分类,适合日常使用

以下是 Git 常用命令 的分类整理&#xff0c;适合日常开发使用。无论你是初学者还是有经验的开发者&#xff0c;这些命令都能帮你高效管理代码版本。 &#x1f331; 一、配置相关 bash 设置用户名和邮箱&#xff08;首次使用 Git 时必须设置&#xff09; git config --global…

作者头像 李华
网站建设 2026/4/3 15:54:18

【CapsLock 失效,Ctrl 键变成 CapsLock 的原因及解决方法】

CapsLock失效&#xff0c;Ctrl键变成CapsLock的原因及解决方法 问题原因分析 这是典型的键盘键位映射错误&#xff0c;主要由以下原因导致&#xff1a; 1. BIOS/UEFI设置问题 许多笔记本电脑&#xff08;尤其是联想、戴尔等品牌&#xff09;在BIOS中默认开启了"Ctrl与…

作者头像 李华
网站建设 2026/4/6 0:19:32

终极指南:3分钟快速上手uni-app跨平台开发

终极指南&#xff1a;3分钟快速上手uni-app跨平台开发 【免费下载链接】uni-app A cross-platform framework using Vue.js 项目地址: https://gitcode.com/dcloud/uni-app uni-app是基于Vue.js的跨平台前端框架&#xff0c;让开发者只需编写一次代码&#xff0c;即可编…

作者头像 李华
网站建设 2026/4/8 9:02:16

Git-Appraise实战指南:解锁分布式代码评审的高效技巧

还在为传统的代码评审流程烦恼吗&#xff1f;每次都要等待中央服务器响应&#xff0c;评审数据无法离线访问&#xff1f;Git-Appraise为你带来全新的分布式代码评审体验&#xff01;&#x1f680; 【免费下载链接】git-appraise Distributed code review system for Git repos …

作者头像 李华