2026/3/10 3:37:34
网站建设
项目流程
制作婚恋网站,qq空间刷赞推广网站,企业网站托管注意事项,上海外贸营销网站建设Avalonia#xff1a;UserControl 与 TemplatedControl
Avalonia 中有两种常见控件创建方式——UserControl#xff08;用户控件#xff09;和 TemplatedControl#xff08;模板控件#xff09;#xff0c;两者分别有不同的使用场景和特点。
很多教程不会辨析两者区别。如…AvaloniaUserControl 与 TemplatedControlAvalonia 中有两种常见控件创建方式——UserControl用户控件和 TemplatedControl模板控件两者分别有不同的使用场景和特点。很多教程不会辨析两者区别。如果初学者比如之前的我没有分清楚两者那会不可避免地写出极其恶心别扭且难以理解的代码。简单概念辨析根据Avalonia官方文档UserControlUserControl 控件是一种 ContentControl它代表了一组在预定义布局中可重用的控件。实际上UserControl 在 ContentControl 的基础上提供的功能非常有限。不同之处在于通常不会直接创建UserControl 类的实例相反通常会为应用程序要显示的每个“视图”创建一个 UserControl 类的新子类。UserControl相比于ContentControl几乎没有添加任何方法或属性。UserControl只是一个可以预定义布局的容器。同时其继承于ContentControl而ContentControl继承于TemplatedControl。因此UserControl 与 TemplatedControl 实际是继承关系。Avalonia模板提供的MainView与MainWindow本质上都是UserControlWindow。TemplatedControl模板控件也称为“Lookless控件”为在Avalonia中创建自定义控件提供了更高级和可自定义的方法。模板控件将控件的行为和逻辑与其可视外观分离允许应用程序开发人员通过控件模板进行样式化和模板化。TemplatedControl 通过后期添加的模板以定义外观布局。类似于TextBox包括所有在不同主题如Fluent中表现不同的均为模板控件。Control为所有控件的基类。PanelBorder等只有后端代码的控件继承于Control。核心区别目的UserControl面向“组合视图”把若干现有控件以 XAML 逻辑组合成一个复用组件通常用于应用层的具体 UI 单元而非这里用用那里用用的基础控件。TemplatedControl面向“可模板化/样式化控件”提供公开属性和可替换的视觉模板ControlTemplate适合库/控件框架中可主题化的控件。可定制性UserControl不建议外部样式改变内部结构适合固定结构。TemplatedControl视觉由 Template、Theme、Style 决定便于主题、样式与模板替换。生命周期与性能UserControl 在构建时直接加载 XAML适合快速组合大量小的 UserControl 可能导致更深的视觉树。TemplatedControl 控件模板延迟实例化和更容易优化、主题化。何时选哪个选择 UserControl 当需要快速组合几个控件形成页面级或区域级 UI类似于MainView不关心内部控件的具体实现。控件结构固定、不需要外部主题化。选择 TemplatedControl 当要构建一个可重用、可主题化且对外提供可绑定属性的控件例如库控件、按钮、复合但可替换样式的控件。希望为不同主题提供不同视觉实现把样式放在 Theme 中。示例UserControlMainWindow.axaml/* by 01022.hk - online tools website : 01022.hk/zh/calclength.html */ Window xmlnshttps://github.com/avaloniaui xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml xmlns:dhttp://schemas.microsoft.com/expression/blend/2008 xmlns:mchttp://schemas.openxmlformats.org/markup-compatibility/2006 xmlns:controlsclr-namespace:ControlDemo.Controls mc:Ignorabled d:DesignWidth800 d:DesignHeight450 x:ClassControlDemo.MainWindow TitleControlDemo controls:UserControl1 PropertyOne1 PropertyTwo2 controls:UserControl1.Template ControlTemplate TextBlock Text1/TextBlock /ControlTemplate /controls:UserControl1.Template /controls:UserControl1 /WindowUserControl1.axaml/* by 01022.hk - online tools website : 01022.hk/zh/calclength.html */ Panel xmlnshttps://github.com/avaloniaui xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml xmlns:dhttp://schemas.microsoft.com/expression/blend/2008 xmlns:mchttp://schemas.openxmlformats.org/markup-compatibility/2006 xmlns:controlsclr-namespace:ControlDemo.Controls mc:Ignorabled d:DesignWidth800 d:DesignHeight450 x:ClassControlDemo.Controls.UserControl1 x:DataTypecontrols:UserControl1 StackPanel TextBlock Text{ Binding PropertyOne, RelativeSource{ RelativeSource AncestorType{x:Type controls:UserControl1}}} PointerPressedPointerPress/ TextBlock NameTwo Text{Binding PropertyTwo}/ /StackPanel /PanelUserControl1.axaml.csusing 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 DirectPropertyUserControl1, int PropertyOneProperty AvaloniaProperty.RegisterDirectUserControl1, 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.BackgroundBrushes.Red; DataContext this; } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } private void PointerPress(object? sender, PointerPressedEventArgs e) { Console.WriteLine(Pointer press); } }注意 以上代码仅供展示用法 是不可运行的 其中糟糕的的写法千万不要学习控件声明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中定义的内容但为什么要这么做呢TemplatedControlTemplatedControl无外观控件将行为与视觉彻底分离控件通过公开的 StyledProperty/DirectProperty 暴露行为和状态外观由 Template 属性中 ControlTemplate 决定。这使得控件可以被主题化、样式化和重用且模板延迟实例化有利于性能优化。关键点暴露属性使用 AvaloniaProperty.Register / RegisterDirect 注册 StyledProperty 或 DirectProperty供模板、样式和绑定使用。模板绑定在 ControlTemplate 中使用 TemplateBinding 来绑定控件属性到视觉树元素。获取模板部件在控件后端重写 OnApplyTemplate或 OnTemplateApplied通过 NameScope 找到带有特定 Name通常以 PART_ 前缀命名的元素并为其绑定事件或行为。适用场景库级控件、可主题化控件、需要高度可定制外观的控件。TemplatedControl1.axaml.csusing Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; public class TemplatedControl1 : TemplatedControl { public static readonly StyledPropertybool IsCheckedProperty AvaloniaProperty.RegisterTemplatedControl1, 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.FindBorder(PART_Thumb); if (thumb ! null) { thumb.BackgroundBrushes.Red; } } }TemplatedControl1.axamlResourceDictionary xmlnshttps://github.com/avaloniaui Style SelectorTemplatedControl1 Setter PropertyTemplate ControlTemplate Border Background{TemplateBinding Background} Padding6 CornerRadius4 Grid Border NamePART_Track Background{TemplateBinding Foreground} CornerRadius2/ Border NamePART_Thumb Width16 Height16 BackgroundWhite HorizontalAlignmentLeft/ /Grid /Border /ControlTemplate /Setter /Style /ResourceDictionaryMainWindow.axamlWindow xmlnshttps://github.com/avaloniaui xmlns:localclr-namespace:ControlDemo.Controls StackPanel local:TemplatedControl1 IsChecked{Binding SomeBool} / local:TemplatedControl1 IsChecked{Binding SomeBool} local:TemplatedControl1.Template ControlTemplate Border Background{TemplateBinding Background} Padding6 CornerRadius4 Grid Border NamePART_Thumb Width16 Height16 BackgroundWhite HorizontalAlignmentLeft/ /Grid /Border /ControlTemplate /local:TemplatedControl1.Template /local:TemplatedControl1 /StackPanel /Window要点如果需要对外公开属性、支持 TemplateBinding、允许主题替换使用 TemplatedControl。在后端使用 StyledProperty 暴露状态在模板中使用 TemplateBinding 实现外观与属性联动。在 OnApplyTemplate 中拾取 PART_* 元素并连接行为或动画保持视觉与逻辑分离。TemplateBindingTemplateBinding 只接受单个属性而不是属性路径且由于性能原因TemplateBinding 只支持 OneWay 模式所以如果你想要使用属性路径进行绑定你必须使用前文提到的RelativeSource。TemplateBinding 没有类型转换功能axaml中绑定的目标必须和属性为同一类型或继承关系TemplateBinding 只能在 IStyledElement 上使用例如GeometryDrawing Brush{TemplateBinding Foreground}/是错误的总结UserControl 与 TemplatedControl 侧重点不同UserControl 以组合固定视图为主适合构建页面级或区域级 UITemplatedControl 以“无外观”模板化为主适合可主题化、可复用的库级控件。可定制性UserControl 结构固定、不鼓励外部通过样式改变内部布局TemplatedControl 通过 Template/Style/Theme 提供高度可替换的视觉表现。属性与绑定TemplatedControl 通过 StyledProperty/DirectProperty 暴露可绑定/样式化的状态UserControl 常用 DataContext / RelativeSource 进行绑定后端代码引用子元素更直接。生命周期与性能UserControl 在加载时直接实例化 XAMLTemplatedControl 的模板延迟实例化更利于主题和性能优化。模板与逻辑分离TemplatedControl 鼓励在 OnApplyTemplate或 TemplateApplied中获取 PART_* 元素并绑定行为保持视觉与逻辑分离UserControl 更适合把视图与行为写在同一处以提高开发效率。选择建议快速组合、结构固定、面向应用层 UI 用 UserControl需要对外公开属性、可主题化或作为控件库提供复用组件时用 TemplatedControl。总体原则按责任选型——页面级组合用 UserControl控件级可替换外观用 TemplatedControl兼顾可维护性与可定制性。