diff --git a/.editorconfig b/.editorconfig
index d5b2badfd5..ee24c310c0 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -212,6 +212,12 @@ indent_size = 2
avalonia_xaml_diagnostic.AVLN2203.severity = error
# StyleInMergedDictionaries
avalonia_xaml_diagnostic.AVLN2204.severity = error
+# RequiredTemplatePartMissing
+avalonia_xaml_diagnostic.AVLN2205.severity = error
+# OptionalTemplatePartMissing
+avalonia_xaml_diagnostic.AVLN2206.severity = info
+# TemplatePartWrongType
+avalonia_xaml_diagnostic.AVLN2207.severity = error
# Obsolete
avalonia_xaml_diagnostic.AVLN5001.severity = error
diff --git a/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml b/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml
index 541385dd9f..03ef86fb61 100644
--- a/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml
+++ b/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml
@@ -23,10 +23,12 @@
-
-
diff --git a/src/Avalonia.Base/Controls/Metadata/TemplatePartAttribute.cs b/src/Avalonia.Base/Controls/Metadata/TemplatePartAttribute.cs
index 3b8f971713..a2a7e70946 100644
--- a/src/Avalonia.Base/Controls/Metadata/TemplatePartAttribute.cs
+++ b/src/Avalonia.Base/Controls/Metadata/TemplatePartAttribute.cs
@@ -5,8 +5,6 @@
using System;
-#nullable enable
-
namespace Avalonia.Controls.Metadata
{
///
@@ -17,7 +15,7 @@ namespace Avalonia.Controls.Metadata
/// Style authors should be able to identify the part type used for styling the specific control.
/// The part is usually required in the style and should have a specific predefined name.
///
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class TemplatePartAttribute : Attribute
{
///
@@ -51,5 +49,10 @@ namespace Avalonia.Controls.Metadata
/// in .
///
public Type Type { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the template part is mandatory to be present in the template.
+ ///
+ public bool IsRequired { get; set; }
}
}
diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
index 03a692f866..0825feb1a7 100644
--- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
+++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
@@ -183,7 +183,8 @@
-
-
(PART_CloseButton) is { } closeButton)
+ {
+ closeButton.Click += (sender, e) => OnClose();
+ }
- var closeButton = e.NameScope.Get
- [TemplatePart("PART_AcceptButton", typeof(Button))]
+ [TemplatePart("PART_AcceptButton", typeof(Button), IsRequired = true)]
[TemplatePart("PART_DayDownButton", typeof(RepeatButton))]
- [TemplatePart("PART_DayHost", typeof(Panel))]
- [TemplatePart("PART_DaySelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("PART_DayHost", typeof(Panel), IsRequired = true)]
+ [TemplatePart("PART_DaySelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_DayUpButton", typeof(RepeatButton))]
[TemplatePart("PART_DismissButton", typeof(Button))]
[TemplatePart("PART_FirstSpacer", typeof(Rectangle))]
[TemplatePart("PART_MonthDownButton", typeof(RepeatButton))]
- [TemplatePart("PART_MonthHost", typeof(Panel))]
- [TemplatePart("PART_MonthSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("PART_MonthHost", typeof(Panel), IsRequired = true)]
+ [TemplatePart("PART_MonthSelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_MonthUpButton", typeof(RepeatButton))]
- [TemplatePart("PART_PickerContainer", typeof(Grid))]
+ [TemplatePart("PART_PickerContainer", typeof(Grid), IsRequired = true)]
[TemplatePart("PART_SecondSpacer", typeof(Rectangle))]
[TemplatePart("PART_YearDownButton", typeof(RepeatButton))]
- [TemplatePart("PART_YearHost", typeof(Panel))]
- [TemplatePart("PART_YearSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("PART_YearHost", typeof(Panel), IsRequired = true)]
+ [TemplatePart("PART_YearSelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_YearUpButton", typeof(RepeatButton))]
public class DatePickerPresenter : PickerPresenterBase
{
diff --git a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
index 929ad68c24..7f19d0545e 100644
--- a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
+++ b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
@@ -11,20 +11,20 @@ namespace Avalonia.Controls
/// Defines the presenter used for selecting a time. Intended for use with
/// but can be used independently
///
- [TemplatePart("PART_AcceptButton", typeof(Button))]
+ [TemplatePart("PART_AcceptButton", typeof(Button), IsRequired = true)]
[TemplatePart("PART_DismissButton", typeof(Button))]
[TemplatePart("PART_HourDownButton", typeof(RepeatButton))]
- [TemplatePart("PART_HourSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("PART_HourSelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_HourUpButton", typeof(RepeatButton))]
[TemplatePart("PART_MinuteDownButton", typeof(RepeatButton))]
- [TemplatePart("PART_MinuteSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("PART_MinuteSelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_MinuteUpButton", typeof(RepeatButton))]
[TemplatePart("PART_PeriodDownButton", typeof(RepeatButton))]
- [TemplatePart("PART_PeriodHost", typeof(Panel))]
- [TemplatePart("PART_PeriodSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("PART_PeriodHost", typeof(Panel), IsRequired = true)]
+ [TemplatePart("PART_PeriodSelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_PeriodUpButton", typeof(RepeatButton))]
- [TemplatePart("PART_PickerContainer", typeof(Grid))]
- [TemplatePart("PART_SecondSpacer", typeof(Rectangle))]
+ [TemplatePart("PART_PickerContainer", typeof(Grid), IsRequired = true)]
+ [TemplatePart("PART_SecondSpacer", typeof(Rectangle), IsRequired = true)]
public class TimePickerPresenter : PickerPresenterBase
{
///
diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
index 25d4a90a9d..0edc798494 100644
--- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
+++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
@@ -19,7 +19,7 @@ namespace Avalonia.Controls
/// Control that represents a TextBox with button spinners that allow incrementing and decrementing numeric values.
///
[TemplatePart("PART_Spinner", typeof(Spinner))]
- [TemplatePart("PART_TextBox", typeof(TextBox))]
+ [TemplatePart("PART_TextBox", typeof(TextBox), IsRequired = true)]
public class NumericUpDown : TemplatedControl
{
///
diff --git a/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs b/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
index ad4d3324bb..a95280e640 100644
--- a/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Collections;
+using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
@@ -10,6 +11,7 @@ namespace Avalonia.Controls.Primitives
///
/// Represents an with a related header.
///
+ [TemplatePart("PART_HeaderPresenter", typeof(ContentPresenter))]
public class HeaderedItemsControl : ItemsControl, IContentPresenterHost
{
private IDisposable? _itemsBinding;
diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs
index 193ed052d4..220931b4f4 100644
--- a/src/Avalonia.Controls/ProgressBar.cs
+++ b/src/Avalonia.Controls/ProgressBar.cs
@@ -13,7 +13,7 @@ namespace Avalonia.Controls
///
/// A control used to indicate the progress of an operation.
///
- [TemplatePart("PART_Indicator", typeof(Border))]
+ [TemplatePart("PART_Indicator", typeof(Border), IsRequired = true)]
[PseudoClasses(":vertical", ":horizontal", ":indeterminate")]
public class ProgressBar : RangeBase
{
diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs
index 6fbfd6f41b..ccb1124e63 100644
--- a/src/Avalonia.Controls/Slider.cs
+++ b/src/Avalonia.Controls/Slider.cs
@@ -45,7 +45,7 @@ namespace Avalonia.Controls
///
[TemplatePart("PART_DecreaseButton", typeof(Button))]
[TemplatePart("PART_IncreaseButton", typeof(Button))]
- [TemplatePart("PART_Track", typeof(Track))]
+ [TemplatePart("PART_Track", typeof(Track), IsRequired = true)]
[PseudoClasses(":vertical", ":horizontal", ":pressed")]
public class Slider : RangeBase
{
@@ -204,13 +204,10 @@ namespace Avalonia.Controls
_pointerMovedDispose?.Dispose();
_decreaseButton = e.NameScope.Find("PART_DecreaseButton");
- _track = e.NameScope.Find