Browse Source

TemplatePart XAML diagnostics (#14180)

* Add TemplatePart.IsRequired property and make attribute inherited

* Implement TemplatePart XAML diagnostics + tests

* Fix new errors in our XAML files

* Ignore nested metadata scopes in TemplatePart validator

* Update docs comment
pull/14736/head
Max Katz 2 years ago
committed by GitHub
parent
commit
180410e076
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      .editorconfig
  2. 6
      samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml
  3. 9
      src/Avalonia.Base/Controls/Metadata/TemplatePartAttribute.cs
  4. 3
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  5. 3
      src/Avalonia.Controls.DataGrid/Themes/Simple.xaml
  6. 33
      src/Avalonia.Controls/Chrome/CaptionButtons.cs
  7. 2
      src/Avalonia.Controls/Chrome/TitleBar.cs
  8. 2
      src/Avalonia.Controls/ComboBox.cs
  9. 16
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  10. 14
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  11. 2
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  12. 2
      src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
  13. 2
      src/Avalonia.Controls/ProgressBar.cs
  14. 9
      src/Avalonia.Controls/Slider.cs
  15. 1
      src/Avalonia.Controls/TabControl.cs
  16. 2
      src/Avalonia.Controls/TextBox.cs
  17. 2
      src/Avalonia.Controls/ToggleSwitch.cs
  18. 4
      src/Avalonia.Dialogs/ManagedFileChooser.cs
  19. 2
      src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml
  20. 12
      src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml
  21. 2
      src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml
  22. 2
      src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml
  23. 2
      src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml
  24. 3
      src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml
  25. 2
      src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml
  26. 3
      src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml
  27. 2
      src/Avalonia.Themes.Simple/Controls/CalendarButton.xaml
  28. 18
      src/Avalonia.Themes.Simple/Controls/CalendarDayButton.xaml
  29. 2
      src/Avalonia.Themes.Simple/Controls/DateTimePickerShared.xaml
  30. 3
      src/Avalonia.Themes.Simple/Controls/FlyoutPresenter.xaml
  31. 13
      src/Avalonia.Themes.Simple/Controls/NotificationCard.xaml
  32. 19
      src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
  33. 3
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlDiagnosticCodes.cs
  34. 1
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  35. 136
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePartsChecker.cs
  36. 11
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs
  37. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
  38. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  39. 13
      src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs
  40. 114
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs

6
.editorconfig

@ -212,6 +212,12 @@ indent_size = 2
avalonia_xaml_diagnostic.AVLN2203.severity = error avalonia_xaml_diagnostic.AVLN2203.severity = error
# StyleInMergedDictionaries # StyleInMergedDictionaries
avalonia_xaml_diagnostic.AVLN2204.severity = error 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 # Obsolete
avalonia_xaml_diagnostic.AVLN5001.severity = error avalonia_xaml_diagnostic.AVLN5001.severity = error

6
samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml

@ -23,10 +23,12 @@
<ColumnDefinition Width="Auto" SharedSizeGroup="HeaderCol" /> <ColumnDefinition Width="Auto" SharedSizeGroup="HeaderCol" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ContentPresenter Content="{TemplateBinding Header}" <ContentPresenter x:Name="PART_HeaderPresenter"
Content="{TemplateBinding Header}"
Grid.Column="0" Grid.Column="0"
VerticalAlignment="Center" /> VerticalAlignment="Center" />
<ContentPresenter Content="{TemplateBinding Content}" <ContentPresenter x:Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Center" /> VerticalAlignment="Center" />
</Grid> </Grid>

9
src/Avalonia.Base/Controls/Metadata/TemplatePartAttribute.cs

@ -5,8 +5,6 @@
using System; using System;
#nullable enable
namespace Avalonia.Controls.Metadata namespace Avalonia.Controls.Metadata
{ {
/// <summary> /// <summary>
@ -17,7 +15,7 @@ namespace Avalonia.Controls.Metadata
/// Style authors should be able to identify the part type used for styling the specific control. /// 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. /// The part is usually required in the style and should have a specific predefined name.
/// </remarks> /// </remarks>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class TemplatePartAttribute : Attribute public sealed class TemplatePartAttribute : Attribute
{ {
/// <summary> /// <summary>
@ -51,5 +49,10 @@ namespace Avalonia.Controls.Metadata
/// in <see cref="Name"/>. /// in <see cref="Name"/>.
/// </summary> /// </summary>
public Type Type { get; set; } public Type Type { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the template part is mandatory to be present in the template.
/// </summary>
public bool IsRequired { get; set; }
} }
} }

3
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@ -183,7 +183,8 @@
<ColumnDefinition Width="Auto" MinWidth="{DynamicResource DataGridSortIconMinWidth}" /> <ColumnDefinition Width="Auto" MinWidth="{DynamicResource DataGridSortIconMinWidth}" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ContentPresenter Content="{TemplateBinding Content}" <ContentPresenter x:Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" /> ContentTemplate="{TemplateBinding ContentTemplate}" />
<Path Name="SortIcon" <Path Name="SortIcon"

3
src/Avalonia.Controls.DataGrid/Themes/Simple.xaml

@ -66,7 +66,8 @@
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
ColumnDefinitions="*,Auto"> ColumnDefinitions="*,Auto">
<ContentPresenter Content="{TemplateBinding Content}" <ContentPresenter x:Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" /> ContentTemplate="{TemplateBinding ContentTemplate}" />
<Path Name="SortIcon" <Path Name="SortIcon"

33
src/Avalonia.Controls/Chrome/CaptionButtons.cs

@ -99,19 +99,28 @@ namespace Avalonia.Controls.Chrome
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
if (e.NameScope.Find<Button>(PART_CloseButton) is { } closeButton)
{
closeButton.Click += (sender, e) => OnClose();
}
var closeButton = e.NameScope.Get<Button>(PART_CloseButton); if (e.NameScope.Find<Button>(PART_RestoreButton) is { } restoreButton)
var restoreButton = e.NameScope.Get<Button>(PART_RestoreButton); {
var minimizeButton = e.NameScope.Get<Button>(PART_MinimizeButton); restoreButton.Click += (sender, e) => OnRestore();
var fullScreenButton = e.NameScope.Get<Button>(PART_FullScreenButton); restoreButton.IsEnabled = HostWindow?.CanResize ?? true;
_restoreButton = restoreButton;
closeButton.Click += (sender, e) => OnClose(); }
restoreButton.Click += (sender, e) => OnRestore();
minimizeButton.Click += (sender, e) => OnMinimize(); if (e.NameScope.Find<Button>(PART_MinimizeButton) is { } minimizeButton)
fullScreenButton.Click += (sender, e) => OnToggleFullScreen(); {
minimizeButton.Click += (sender, e) => OnMinimize();
restoreButton.IsEnabled = HostWindow?.CanResize ?? true; }
_restoreButton = restoreButton;
if (e.NameScope.Find<Button>(PART_FullScreenButton) is { } fullScreenButton)
{
fullScreenButton.Click += (sender, e) => OnToggleFullScreen();
}
} }
} }
} }

2
src/Avalonia.Controls/Chrome/TitleBar.cs

@ -8,7 +8,7 @@ namespace Avalonia.Controls.Chrome
/// <summary> /// <summary>
/// Draws a titlebar when managed client decorations are enabled. /// Draws a titlebar when managed client decorations are enabled.
/// </summary> /// </summary>
[TemplatePart("PART_CaptionButtons", typeof(CaptionButtons))] [TemplatePart("PART_CaptionButtons", typeof(CaptionButtons), IsRequired = true)]
[PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")] [PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")]
public class TitleBar : TemplatedControl public class TitleBar : TemplatedControl
{ {

2
src/Avalonia.Controls/ComboBox.cs

@ -17,7 +17,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A drop-down list control. /// A drop-down list control.
/// </summary> /// </summary>
[TemplatePart("PART_Popup", typeof(Popup))] [TemplatePart("PART_Popup", typeof(Popup), IsRequired = true)]
[PseudoClasses(pcDropdownOpen, pcPressed)] [PseudoClasses(pcDropdownOpen, pcPressed)]
public class ComboBox : SelectingItemsControl public class ComboBox : SelectingItemsControl
{ {

16
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@ -13,22 +13,22 @@ namespace Avalonia.Controls
/// Defines the presenter used for selecting a date for a /// Defines the presenter used for selecting a date for a
/// <see cref="DatePicker"/> /// <see cref="DatePicker"/>
/// </summary> /// </summary>
[TemplatePart("PART_AcceptButton", typeof(Button))] [TemplatePart("PART_AcceptButton", typeof(Button), IsRequired = true)]
[TemplatePart("PART_DayDownButton", typeof(RepeatButton))] [TemplatePart("PART_DayDownButton", typeof(RepeatButton))]
[TemplatePart("PART_DayHost", typeof(Panel))] [TemplatePart("PART_DayHost", typeof(Panel), IsRequired = true)]
[TemplatePart("PART_DaySelector", typeof(DateTimePickerPanel))] [TemplatePart("PART_DaySelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_DayUpButton", typeof(RepeatButton))] [TemplatePart("PART_DayUpButton", typeof(RepeatButton))]
[TemplatePart("PART_DismissButton", typeof(Button))] [TemplatePart("PART_DismissButton", typeof(Button))]
[TemplatePart("PART_FirstSpacer", typeof(Rectangle))] [TemplatePart("PART_FirstSpacer", typeof(Rectangle))]
[TemplatePart("PART_MonthDownButton", typeof(RepeatButton))] [TemplatePart("PART_MonthDownButton", typeof(RepeatButton))]
[TemplatePart("PART_MonthHost", typeof(Panel))] [TemplatePart("PART_MonthHost", typeof(Panel), IsRequired = true)]
[TemplatePart("PART_MonthSelector", typeof(DateTimePickerPanel))] [TemplatePart("PART_MonthSelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_MonthUpButton", typeof(RepeatButton))] [TemplatePart("PART_MonthUpButton", typeof(RepeatButton))]
[TemplatePart("PART_PickerContainer", typeof(Grid))] [TemplatePart("PART_PickerContainer", typeof(Grid), IsRequired = true)]
[TemplatePart("PART_SecondSpacer", typeof(Rectangle))] [TemplatePart("PART_SecondSpacer", typeof(Rectangle))]
[TemplatePart("PART_YearDownButton", typeof(RepeatButton))] [TemplatePart("PART_YearDownButton", typeof(RepeatButton))]
[TemplatePart("PART_YearHost", typeof(Panel))] [TemplatePart("PART_YearHost", typeof(Panel), IsRequired = true)]
[TemplatePart("PART_YearSelector", typeof(DateTimePickerPanel))] [TemplatePart("PART_YearSelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_YearUpButton", typeof(RepeatButton))] [TemplatePart("PART_YearUpButton", typeof(RepeatButton))]
public class DatePickerPresenter : PickerPresenterBase public class DatePickerPresenter : PickerPresenterBase
{ {

14
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 /// Defines the presenter used for selecting a time. Intended for use with
/// <see cref="TimePicker"/> but can be used independently /// <see cref="TimePicker"/> but can be used independently
/// </summary> /// </summary>
[TemplatePart("PART_AcceptButton", typeof(Button))] [TemplatePart("PART_AcceptButton", typeof(Button), IsRequired = true)]
[TemplatePart("PART_DismissButton", typeof(Button))] [TemplatePart("PART_DismissButton", typeof(Button))]
[TemplatePart("PART_HourDownButton", typeof(RepeatButton))] [TemplatePart("PART_HourDownButton", typeof(RepeatButton))]
[TemplatePart("PART_HourSelector", typeof(DateTimePickerPanel))] [TemplatePart("PART_HourSelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_HourUpButton", typeof(RepeatButton))] [TemplatePart("PART_HourUpButton", typeof(RepeatButton))]
[TemplatePart("PART_MinuteDownButton", 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_MinuteUpButton", typeof(RepeatButton))]
[TemplatePart("PART_PeriodDownButton", typeof(RepeatButton))] [TemplatePart("PART_PeriodDownButton", typeof(RepeatButton))]
[TemplatePart("PART_PeriodHost", typeof(Panel))] [TemplatePart("PART_PeriodHost", typeof(Panel), IsRequired = true)]
[TemplatePart("PART_PeriodSelector", typeof(DateTimePickerPanel))] [TemplatePart("PART_PeriodSelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_PeriodUpButton", typeof(RepeatButton))] [TemplatePart("PART_PeriodUpButton", typeof(RepeatButton))]
[TemplatePart("PART_PickerContainer", typeof(Grid))] [TemplatePart("PART_PickerContainer", typeof(Grid), IsRequired = true)]
[TemplatePart("PART_SecondSpacer", typeof(Rectangle))] [TemplatePart("PART_SecondSpacer", typeof(Rectangle), IsRequired = true)]
public class TimePickerPresenter : PickerPresenterBase public class TimePickerPresenter : PickerPresenterBase
{ {
/// <summary> /// <summary>

2
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. /// Control that represents a TextBox with button spinners that allow incrementing and decrementing numeric values.
/// </summary> /// </summary>
[TemplatePart("PART_Spinner", typeof(Spinner))] [TemplatePart("PART_Spinner", typeof(Spinner))]
[TemplatePart("PART_TextBox", typeof(TextBox))] [TemplatePart("PART_TextBox", typeof(TextBox), IsRequired = true)]
public class NumericUpDown : TemplatedControl public class NumericUpDown : TemplatedControl
{ {
/// <summary> /// <summary>

2
src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs

@ -1,5 +1,6 @@
using System; using System;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
@ -10,6 +11,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Represents an <see cref="ItemsControl"/> with a related header. /// Represents an <see cref="ItemsControl"/> with a related header.
/// </summary> /// </summary>
[TemplatePart("PART_HeaderPresenter", typeof(ContentPresenter))]
public class HeaderedItemsControl : ItemsControl, IContentPresenterHost public class HeaderedItemsControl : ItemsControl, IContentPresenterHost
{ {
private IDisposable? _itemsBinding; private IDisposable? _itemsBinding;

2
src/Avalonia.Controls/ProgressBar.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A control used to indicate the progress of an operation. /// A control used to indicate the progress of an operation.
/// </summary> /// </summary>
[TemplatePart("PART_Indicator", typeof(Border))] [TemplatePart("PART_Indicator", typeof(Border), IsRequired = true)]
[PseudoClasses(":vertical", ":horizontal", ":indeterminate")] [PseudoClasses(":vertical", ":horizontal", ":indeterminate")]
public class ProgressBar : RangeBase public class ProgressBar : RangeBase
{ {

9
src/Avalonia.Controls/Slider.cs

@ -45,7 +45,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
[TemplatePart("PART_DecreaseButton", typeof(Button))] [TemplatePart("PART_DecreaseButton", typeof(Button))]
[TemplatePart("PART_IncreaseButton", typeof(Button))] [TemplatePart("PART_IncreaseButton", typeof(Button))]
[TemplatePart("PART_Track", typeof(Track))] [TemplatePart("PART_Track", typeof(Track), IsRequired = true)]
[PseudoClasses(":vertical", ":horizontal", ":pressed")] [PseudoClasses(":vertical", ":horizontal", ":pressed")]
public class Slider : RangeBase public class Slider : RangeBase
{ {
@ -204,13 +204,10 @@ namespace Avalonia.Controls
_pointerMovedDispose?.Dispose(); _pointerMovedDispose?.Dispose();
_decreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton"); _decreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton");
_track = e.NameScope.Find<Track>("PART_Track"); _track = e.NameScope.Get<Track>("PART_Track");
_increaseButton = e.NameScope.Find<Button>("PART_IncreaseButton"); _increaseButton = e.NameScope.Find<Button>("PART_IncreaseButton");
if (_track != null) _track.IgnoreThumbDrag = true;
{
_track.IgnoreThumbDrag = true;
}
if (_decreaseButton != null) if (_decreaseButton != null)
{ {

1
src/Avalonia.Controls/TabControl.cs

@ -18,6 +18,7 @@ namespace Avalonia.Controls
/// A tab control that displays a tab strip along with the content of the selected tab. /// A tab control that displays a tab strip along with the content of the selected tab.
/// </summary> /// </summary>
[TemplatePart("PART_ItemsPresenter", typeof(ItemsPresenter))] [TemplatePart("PART_ItemsPresenter", typeof(ItemsPresenter))]
[TemplatePart("PART_SelectedContentHost", typeof(ContentPresenter))]
public class TabControl : SelectingItemsControl, IContentPresenterHost public class TabControl : SelectingItemsControl, IContentPresenterHost
{ {
private object? _selectedContent; private object? _selectedContent;

2
src/Avalonia.Controls/TextBox.cs

@ -24,7 +24,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Represents a control that can be used to display or edit unformatted text. /// Represents a control that can be used to display or edit unformatted text.
/// </summary> /// </summary>
[TemplatePart("PART_TextPresenter", typeof(TextPresenter))] [TemplatePart("PART_TextPresenter", typeof(TextPresenter), IsRequired = true)]
[TemplatePart("PART_ScrollViewer", typeof(ScrollViewer))] [TemplatePart("PART_ScrollViewer", typeof(ScrollViewer))]
[PseudoClasses(":empty")] [PseudoClasses(":empty")]
public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost

2
src/Avalonia.Controls/ToggleSwitch.cs

@ -11,7 +11,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A Toggle Switch control. /// A Toggle Switch control.
/// </summary> /// </summary>
[TemplatePart("PART_MovingKnobs", typeof(Panel))] [TemplatePart("PART_MovingKnobs", typeof(Panel), IsRequired = true)]
[TemplatePart("PART_OffContentPresenter", typeof(ContentPresenter))] [TemplatePart("PART_OffContentPresenter", typeof(ContentPresenter))]
[TemplatePart("PART_OnContentPresenter", typeof(ContentPresenter))] [TemplatePart("PART_OnContentPresenter", typeof(ContentPresenter))]
[TemplatePart("PART_SwitchKnob", typeof(Panel))] [TemplatePart("PART_SwitchKnob", typeof(Panel))]

4
src/Avalonia.Dialogs/ManagedFileChooser.cs

@ -11,8 +11,8 @@ using Avalonia.LogicalTree;
namespace Avalonia.Dialogs namespace Avalonia.Dialogs
{ {
[TemplatePart("PART_QuickLinks", typeof(Control))] [TemplatePart("PART_QuickLinks", typeof(Control), IsRequired = true)]
[TemplatePart("PART_Files", typeof(ListBox))] [TemplatePart("PART_Files", typeof(ListBox), IsRequired = true)]
public class ManagedFileChooser : TemplatedControl public class ManagedFileChooser : TemplatedControl
{ {
private Control? _quickLinksRoot; private Control? _quickLinksRoot;

2
src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml

@ -35,7 +35,7 @@
<!-- To mimic WinUI SystemFocusVisual, Focus visual is drawn outside the bounds of the item --> <!-- To mimic WinUI SystemFocusVisual, Focus visual is drawn outside the bounds of the item -->
<Border Name="Root" Background="{TemplateBinding Background}" <Border Name="Root" Background="{TemplateBinding Background}"
BorderThickness="0" ClipToBounds="True"> BorderThickness="0" ClipToBounds="True">
<ContentPresenter Name="Content" <ContentPresenter Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"

12
src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml

@ -36,12 +36,12 @@
<Border Name="Root" Background="{TemplateBinding Background}" <Border Name="Root" Background="{TemplateBinding Background}"
BorderThickness="0" ClipToBounds="True"> BorderThickness="0" ClipToBounds="True">
<ContentControl Name="Content" <ContentPresenter Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}" /> Margin="{TemplateBinding Padding}" />
</Border> </Border>

2
src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml

@ -31,7 +31,7 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<!-- HCA was changed here to ensure nav arrows display correctly --> <!-- HCA was changed here to ensure nav arrows display correctly -->
<ContentPresenter Name="Text" Background="{TemplateBinding Background}" <ContentPresenter Name="PART_ContentPresenter" Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource CalendarViewNavigationButtonBorderBrush}" BorderBrush="{DynamicResource CalendarViewNavigationButtonBorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"

2
src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml

@ -57,7 +57,7 @@
</Panel> </Panel>
</Viewbox> </Viewbox>
</Grid> </Grid>
<ContentPresenter x:Name="ContentPresenter" <ContentPresenter x:Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}"

2
src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml

@ -55,7 +55,7 @@
that appear opaque. Not sure how MS does it though I suspect this is it that appear opaque. Not sure how MS does it though I suspect this is it
but source isn't MIT yet, so this is my solution --> but source isn't MIT yet, so this is my solution -->
<Border Background="{TemplateBinding Background}"> <Border Background="{TemplateBinding Background}">
<ContentPresenter x:Name="ContentPresenter" <ContentPresenter x:Name="PART_ContentPresenter"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource DateTimePickerFlyoutButtonBorderBrush}" BorderBrush="{DynamicResource DateTimePickerFlyoutButtonBorderBrush}"
BorderThickness="{DynamicResource DateTimeFlyoutButtonBorderThickness}" BorderThickness="{DynamicResource DateTimeFlyoutButtonBorderThickness}"

3
src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml

@ -26,7 +26,8 @@
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"> VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<ContentPresenter Content="{TemplateBinding Content}" <ContentPresenter x:Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"

2
src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml

@ -25,7 +25,7 @@
ClipToBounds="True"> ClipToBounds="True">
<DockPanel> <DockPanel>
<Panel x:Name="PART_HeaderBar" Height="4" Background="{DynamicResource NotificationCardProgressBackgroundBrush}" DockPanel.Dock="Top" /> <Panel x:Name="PART_HeaderBar" Height="4" Background="{DynamicResource NotificationCardProgressBackgroundBrush}" DockPanel.Dock="Top" />
<ContentControl Name="PART_Content" MinHeight="64" Content="{TemplateBinding Content}" /> <ContentPresenter Name="PART_ContentPresenter" MinHeight="64" Content="{TemplateBinding Content}" />
</DockPanel> </DockPanel>
</Border> </Border>
</Border> </Border>

3
src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml

@ -63,7 +63,8 @@
<Setter Property="TextElement.Foreground" Value="{DynamicResource ScrollBarButtonArrowForeground}"/> <Setter Property="TextElement.Foreground" Value="{DynamicResource ScrollBarButtonArrowForeground}"/>
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<ContentPresenter Background="{DynamicResource ScrollBarButtonBackground}" <ContentPresenter x:Name="PART_ContentPresenter"
Background="{DynamicResource ScrollBarButtonBackground}"
BorderBrush="{DynamicResource ScrollBarButtonBorderBrush}" BorderBrush="{DynamicResource ScrollBarButtonBorderBrush}"
Content="{TemplateBinding Content}"/> Content="{TemplateBinding Content}"/>
</ControlTemplate> </ControlTemplate>

2
src/Avalonia.Themes.Simple/Controls/CalendarButton.xaml

@ -33,7 +33,7 @@
Opacity="0.5" /> Opacity="0.5" />
<!-- Focusable="False" --> <!-- Focusable="False" -->
<ContentPresenter Name="Content" <ContentPresenter x:Name="PART_ContentPresenter"
Margin="1,0,1,1" Margin="1,0,1,1"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"

18
src/Avalonia.Themes.Simple/Controls/CalendarDayButton.xaml

@ -36,15 +36,15 @@
IsVisible="False" IsVisible="False"
Opacity="0.5" /> Opacity="0.5" />
<ContentControl Name="Content" <ContentPresenter Name="PART_ContentPresenter"
Margin="5,1,5,1" Margin="5,1,5,1"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
FontSize="{TemplateBinding FontSize}" FontSize="{TemplateBinding FontSize}"
Foreground="{TemplateBinding Foreground}" Foreground="{TemplateBinding Foreground}"
Opacity="1" /> Opacity="1" />
<Path Name="BlackoutVisual" <Path Name="BlackoutVisual"
Margin="3" Margin="3"

2
src/Avalonia.Themes.Simple/Controls/DateTimePickerShared.xaml

@ -64,7 +64,7 @@
but source isn't MIT yet, so this is my solution but source isn't MIT yet, so this is my solution
--> -->
<Border Background="{TemplateBinding Background}"> <Border Background="{TemplateBinding Background}">
<ContentPresenter x:Name="ContentPresenter" <ContentPresenter x:Name="PART_ContentPresenter"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"

3
src/Avalonia.Themes.Simple/Controls/FlyoutPresenter.xaml

@ -20,7 +20,8 @@
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"> VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<ContentPresenter Margin="{TemplateBinding Padding}" <ContentPresenter x:Name="PART_ContentPresenter"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="Stretch" HorizontalContentAlignment="Stretch"

13
src/Avalonia.Themes.Simple/Controls/NotificationCard.xaml

@ -13,15 +13,14 @@
<ControlTemplate> <ControlTemplate>
<LayoutTransformControl Name="PART_LayoutTransformControl" <LayoutTransformControl Name="PART_LayoutTransformControl"
UseRenderTransform="True"> UseRenderTransform="True">
<Border Margin="8,8,0,0" <ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentControl Name="PART_Content"
MinHeight="150" MinHeight="150"
Padding="8,8,0,0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
Content="{TemplateBinding Content}" /> Content="{TemplateBinding Content}" />
</Border>
</LayoutTransformControl> </LayoutTransformControl>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>

19
src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs

@ -215,14 +215,27 @@ namespace Avalonia.Markup.Xaml.XamlIl
HandleDiagnostic = (diagnostic) => HandleDiagnostic = (diagnostic) =>
{ {
var runtimeDiagnostic = new RuntimeXamlDiagnostic(diagnostic.Code.ToString(), var runtimeDiagnostic = new RuntimeXamlDiagnostic(diagnostic.Code.ToString(),
(RuntimeXamlDiagnosticSeverity)diagnostic.Severity, diagnostic.Severity switch
{
XamlDiagnosticSeverity.None => RuntimeXamlDiagnosticSeverity.Info,
XamlDiagnosticSeverity.Warning => RuntimeXamlDiagnosticSeverity.Warning,
XamlDiagnosticSeverity.Error => RuntimeXamlDiagnosticSeverity.Error,
XamlDiagnosticSeverity.Fatal => RuntimeXamlDiagnosticSeverity.Fatal,
_ => throw new ArgumentOutOfRangeException()
},
diagnostic.Title, diagnostic.LineNumber, diagnostic.LinePosition) diagnostic.Title, diagnostic.LineNumber, diagnostic.LinePosition)
{ {
Document = diagnostic.Document Document = diagnostic.Document
}; };
var newSeverity = var newSeverity =
(XamlDiagnosticSeverity?)configuration.DiagnosticHandler?.Invoke(runtimeDiagnostic) ?? configuration.DiagnosticHandler?.Invoke(runtimeDiagnostic) switch
diagnostic.Severity; {
RuntimeXamlDiagnosticSeverity.Info => XamlDiagnosticSeverity.None,
RuntimeXamlDiagnosticSeverity.Warning => XamlDiagnosticSeverity.Warning,
RuntimeXamlDiagnosticSeverity.Error => XamlDiagnosticSeverity.Error,
RuntimeXamlDiagnosticSeverity.Fatal => XamlDiagnosticSeverity.Fatal,
_ => (XamlDiagnosticSeverity?)null
} ?? diagnostic.Severity;
diagnostic = diagnostic with { Severity = newSeverity }; diagnostic = diagnostic with { Severity = newSeverity };
diagnostics.Add(diagnostic); diagnostics.Add(diagnostic);
return newSeverity; return newSeverity;

3
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlDiagnosticCodes.cs

@ -25,6 +25,9 @@ internal static class AvaloniaXamlDiagnosticCodes
public const string PropertyPathError = "AVLN2202"; public const string PropertyPathError = "AVLN2202";
public const string DuplicateSetterError = "AVLN2203"; public const string DuplicateSetterError = "AVLN2203";
public const string StyleInMergedDictionaries = "AVLN2204"; public const string StyleInMergedDictionaries = "AVLN2204";
public const string RequiredTemplatePartMissing = "AVLN2205";
public const string OptionalTemplatePartMissing = "AVLN2206";
public const string TemplatePartWrongType = "AVLN2207";
// XAML emit errors 3000-3999. // XAML emit errors 3000-3999.
public const string EmitError = "AVLN3000"; public const string EmitError = "AVLN3000";

1
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -76,6 +76,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
// After everything else // After everything else
InsertBefore<NewObjectTransformer>( InsertBefore<NewObjectTransformer>(
new AddNameScopeRegistration(), new AddNameScopeRegistration(),
new AvaloniaXamlIlControlTemplatePartsChecker(),
new AvaloniaXamlIlDataContextTypeTransformer(), new AvaloniaXamlIlDataContextTypeTransformer(),
new AvaloniaXamlIlBindingPathTransformer(), new AvaloniaXamlIlBindingPathTransformer(),
new AvaloniaXamlIlCompiledBindingsMetadataRemover() new AvaloniaXamlIlCompiledBindingsMetadataRemover()

136
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePartsChecker.cs

@ -0,0 +1,136 @@
using System.Collections.Generic;
using System.Linq;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
internal class AvaloniaXamlIlControlTemplatePartsChecker : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is AvaloniaXamlIlTargetTypeMetadataNode on
&& on.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate
// Styles with template selector will also return ScopeTypes.ControlTemplate, so we need to double check.
&& on.Value.Type.GetClrType() == context.GetAvaloniaTypes().ControlTemplate))
return node;
var targetType = on.TargetType.GetClrType();
var templateParts = ResolveTemplateParts(targetType);
if (templateParts.Count == 0)
return node;
var visitor = new TemplatePartVisitor();
node.VisitChildren(visitor);
foreach (var pair in templateParts)
{
var name = pair.Key;
var (expectedType, isRequired) = pair.Value;
if (!visitor.TryGetValue(name, out var res))
{
if (isRequired)
{
context.ReportDiagnostic(new XamlDiagnostic(
AvaloniaXamlDiagnosticCodes.RequiredTemplatePartMissing,
XamlDiagnosticSeverity.Error,
$"Required template part with x:Name '{name}' must be defined on '{targetType.Name}' ControlTemplate.",
node));
}
else
{
context.ReportDiagnostic(new XamlDiagnostic(
AvaloniaXamlDiagnosticCodes.OptionalTemplatePartMissing,
XamlDiagnosticSeverity.None,
$"Optional template part with x:Name '{name}' can be defined on '{targetType.Name}' ControlTemplate.",
node));
}
continue;
}
if (expectedType is not null
&& !expectedType.IsAssignableFrom(res.type))
{
context.ReportDiagnostic(new XamlDiagnostic(
AvaloniaXamlDiagnosticCodes.TemplatePartWrongType,
XamlDiagnosticSeverity.Error,
$"Template part '{name}' is expected to be assignable to '{expectedType.Name}', but actual type is {res.type.Name}.",
res.line));
}
}
return node;
}
private static Dictionary<string, (IXamlType? type, bool isRequired)> ResolveTemplateParts(IXamlType targetType)
{
var dictionary = new Dictionary<string, (IXamlType? type, bool isRequired)>();
// Custom Attributes go in order from current type to base type. It should be possible to override parent template parts.
foreach (var attr in targetType.GetAllCustomAttributes())
{
if (attr.Type.Name == "TemplatePartAttribute")
{
if (!attr.Properties.TryGetValue("Name", out var nameObj))
{
nameObj = attr.Parameters.FirstOrDefault();
}
if (!attr.Properties.TryGetValue("Type", out var typeObj))
{
typeObj = attr.Parameters.Skip(1).FirstOrDefault();
}
attr.Properties.TryGetValue("IsRequired", out var isRequiredObj);
if (nameObj is string { Length :> 0 } name
&& !dictionary.ContainsKey(name))
{
dictionary.Add(name, (typeObj as IXamlType, isRequiredObj as bool? == true));
}
}
}
return dictionary;
}
private class TemplatePartVisitor : Dictionary<string, (IXamlType type, IXamlLineInfo line)>, IXamlAstVisitor
{
private int _metadataScopeLevel = 0;
private Stack<IXamlAstNode> _parents = new();
IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
{
if (_metadataScopeLevel == 1
&& node is AvaloniaNameScopeRegistrationXamlIlNode nameScopeRegistration
&& nameScopeRegistration.Name is XamlAstTextNode textNode)
{
this[textNode.Text] = (nameScopeRegistration.TargetType, textNode);
}
return node;
}
void IXamlAstVisitor.Push(IXamlAstNode node)
{
_parents.Push(node);
if (node is NestedScopeMetadataNode)
{
_metadataScopeLevel++;
}
}
void IXamlAstVisitor.Pop()
{
var oldParent = _parents.Pop();
if (oldParent is NestedScopeMetadataNode)
{
_metadataScopeLevel--;
}
}
}
}

11
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs

@ -21,7 +21,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
// - The property has a single value // - The property has a single value
if (node is XamlPropertyAssignmentNode prop && if (node is XamlPropertyAssignmentNode prop &&
prop.Property is XamlIlAvaloniaProperty avaloniaProperty && prop.Property is XamlIlAvaloniaProperty avaloniaProperty &&
context.ParentNodes().Any(IsControlTemplate) && context.ParentNodes().Any(c => c is AvaloniaXamlIlTargetTypeMetadataNode
{
ScopeType: AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate
}) &&
prop.Values.Count == 1) prop.Values.Count == 1)
{ {
var priorityValueSetters = new List<IXamlPropertySetter>(); var priorityValueSetters = new List<IXamlPropertySetter>();
@ -48,11 +51,5 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node; return node;
} }
private static bool IsControlTemplate(IXamlAstNode node)
{
return node is AvaloniaXamlIlTargetTypeMetadataNode tt &&
tt.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate;
}
} }
} }

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs

@ -11,7 +11,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{ {
if (!(node is XamlAstObjectNode on if (!(node is XamlAstObjectNode on
&& on.Type.GetClrType().FullName == "Avalonia.Markup.Xaml.Templates.ControlTemplate")) && on.Type.GetClrType() == context.GetAvaloniaTypes().ControlTemplate))
return node; return node;
var tt = on.Children.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(ch => var tt = on.Children.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(ch =>
ch.Property.GetClrProperty().Name == "TargetType"); ch.Property.GetClrProperty().Name == "TargetType");

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -120,6 +120,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType ControlTheme { get; } public IXamlType ControlTheme { get; }
public IXamlType WindowTransparencyLevel { get; } public IXamlType WindowTransparencyLevel { get; }
public IXamlType IReadOnlyListOfT { get; } public IXamlType IReadOnlyListOfT { get; }
public IXamlType ControlTemplate { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{ {
@ -264,6 +265,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
UriConstructor = Uri.GetConstructor(new List<IXamlType>() { cfg.WellKnownTypes.String, UriKind }); UriConstructor = Uri.GetConstructor(new List<IXamlType>() { cfg.WellKnownTypes.String, UriKind });
Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style"); Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style");
ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme"); ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme");
ControlTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.ControlTemplate");
IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1"); IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1");
} }
} }

13
src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs

@ -39,13 +39,18 @@ public class RuntimeXamlLoaderConfiguration
public enum RuntimeXamlDiagnosticSeverity public enum RuntimeXamlDiagnosticSeverity
{ {
None = 0, /// <summary>
/// Something that is an issue, as determined by some authority,
/// but is not surfaced through normal means.
/// There may be different mechanisms that act on these issues.
/// </summary>
Info = 1,
/// <summary> /// <summary>
/// Diagnostic is reported as a warning. /// Diagnostic is reported as a warning.
/// </summary> /// </summary>
Warning = 1, Warning,
/// <summary> /// <summary>
/// Diagnostic is reported as an error. /// Diagnostic is reported as an error.
/// Compilation process is continued until the end of the parsing and transforming stage, throwing an aggregated exception of all errors. /// Compilation process is continued until the end of the parsing and transforming stage, throwing an aggregated exception of all errors.

114
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs

@ -1,6 +1,9 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data; using Avalonia.Data;
@ -276,7 +279,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
<ControlTemplate xmlns='https://github.com/avaloniaui' <ControlTemplate xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
TargetType='{x:Type ContentControl}'> TargetType='{x:Type ContentControl}'>
<ContentPresenter Content='{TemplateBinding Content}' /> <ContentPresenter x:Name='PART_ContentPresenter' Content='{TemplateBinding Content}' />
</ControlTemplate> </ControlTemplate>
"; ";
var template = AvaloniaRuntimeXamlLoader.Parse<ControlTemplate>(xaml); var template = AvaloniaRuntimeXamlLoader.Parse<ControlTemplate>(xaml);
@ -293,7 +296,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
<ControlTemplate xmlns='https://github.com/avaloniaui' <ControlTemplate xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
TargetType='ContentControl'> TargetType='ContentControl'>
<ContentPresenter Content='{TemplateBinding Content}' /> <ContentPresenter x:Name='PART_ContentPresenter' Content='{TemplateBinding Content}' />
</ControlTemplate> </ControlTemplate>
"; ";
var template = AvaloniaRuntimeXamlLoader.Parse<ControlTemplate>(xaml); var template = AvaloniaRuntimeXamlLoader.Parse<ControlTemplate>(xaml);
@ -337,6 +340,103 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var templateResult = template.Build(new TemplatedControl()); var templateResult = template.Build(new TemplatedControl());
Assert.Null(templateResult); Assert.Null(templateResult);
} }
[Fact]
public void ControlTemplate_Outputs_Error_When_Missing_TemplatePart()
{
using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
var xaml = @"
<ControlTemplate xmlns='https://github.com/avaloniaui'
xmlns:controls='using:Avalonia.Markup.Xaml.UnitTests.Xaml'
TargetType='controls:CustomButtonWithParts'>
<Border Name='PART_Typo_MainContentBorder'>
<ContentPresenter Name='PART_ContentPresenter'
Content='{TemplateBinding Content}'/>
</Border>
</ControlTemplate>";
var diagnostics = new List<RuntimeXamlDiagnostic>();
AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml), new RuntimeXamlLoaderConfiguration
{
LocalAssembly = typeof(XamlIlTests).Assembly,
DiagnosticHandler = diagnostic =>
{
diagnostics.Add(diagnostic);
return diagnostic.Severity;
}
});
var warning = Assert.Single(diagnostics);
Assert.Equal(RuntimeXamlDiagnosticSeverity.Info, warning.Severity);
Assert.Contains("'PART_MainContentBorder'", warning.Title);
}
[Fact]
public void ControlTemplate_Outputs_Error_When_Using_Wrong_Type_With_TemplatePart()
{
using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
var xaml = @"
<ControlTemplate xmlns='https://github.com/avaloniaui'
xmlns:controls='using:Avalonia.Markup.Xaml.UnitTests.Xaml'
TargetType='controls:CustomControlWithParts'>
<Border Name='PART_MainContentBorder'>
<ContentControl Name='PART_ContentPresenter'
Content='{TemplateBinding Content}'/>
</Border>
</ControlTemplate>";
var diagnostics = new List<RuntimeXamlDiagnostic>();
Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml), new RuntimeXamlLoaderConfiguration
{
LocalAssembly = typeof(XamlIlTests).Assembly,
DiagnosticHandler = diagnostic =>
{
diagnostics.Add(diagnostic);
return diagnostic.Severity;
}
}));
var warning = Assert.Single(diagnostics);
Assert.Equal(RuntimeXamlDiagnosticSeverity.Error, warning.Severity);
Assert.Contains("'ContentPresenter'", warning.Title);
}
[Fact]
public void ControlTemplate_Outputs_Error_When_Missing_TemplatePart_Nested_ItemTemplate_Case()
{
using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
var xaml = @"
<ControlTemplate xmlns='https://github.com/avaloniaui'
xmlns:controls='using:Avalonia.Markup.Xaml.UnitTests.Xaml'
TargetType='controls:CustomControlWithParts'>
<Border Name='PART_Typo_MainContentBorder'>
<StackPanel>
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- This PART_MainContentBorder shouldn't full parent ControlTemplate, PART_Typo_MainContentBorder still isn't properly named. -->
<Border Name='PART_MainContentBorder' />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ContentPresenter Name='PART_ContentPresenter'
Content='{TemplateBinding Content}'/>
</StackPanel>
</Border>
</ControlTemplate>";
var diagnostics = new List<RuntimeXamlDiagnostic>();
AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml), new RuntimeXamlLoaderConfiguration
{
LocalAssembly = typeof(XamlIlTests).Assembly,
DiagnosticHandler = diagnostic =>
{
diagnostics.Add(diagnostic);
return diagnostic.Severity;
}
});
var warning = Assert.Single(diagnostics);
Assert.Equal(RuntimeXamlDiagnosticSeverity.Info, warning.Severity);
Assert.Contains("'PART_MainContentBorder'", warning.Title);
}
} }
public class ListBoxHierarchyLine : Panel public class ListBoxHierarchyLine : Panel
{ {
@ -349,4 +449,14 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
set => SetValue(LineDashStyleProperty, value); set => SetValue(LineDashStyleProperty, value);
} }
} }
[TemplatePart("PART_MainContentBorder", typeof(Border))]
[TemplatePart("PART_ContentPresenter", typeof(ContentPresenter))]
public class CustomControlWithParts : ContentControl
{
}
public class CustomButtonWithParts : CustomControlWithParts
{
}
} }

Loading…
Cancel
Save