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
- <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>
复制代码注意 以上代码仅供展示用法 是不可运行的 其中糟糕的的写法千万不要学习
- 控件声明
- x:Class 指向后端代码对于的类名,x
ataType 可提供编译时的强类型绑定提示。
- x
ataType 代表强类型的 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
- TemplatedControl1.axaml
- <ResourceDictionary xmlns="https://github.com/avaloniaui">
-
- </ResourceDictionary>
复制代码 - MainWindow.axaml
- 要点
- 如果需要对外公开属性、支持 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,兼顾可维护性与可定制性。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |