diff --git a/src/Avalonia.Controls/Templates/DataTemplates.cs b/src/Avalonia.Controls/Templates/DataTemplates.cs
index f203539536..d4eeda7908 100644
--- a/src/Avalonia.Controls/Templates/DataTemplates.cs
+++ b/src/Avalonia.Controls/Templates/DataTemplates.cs
@@ -1,3 +1,4 @@
+using System;
using Avalonia.Collections;
namespace Avalonia.Controls.Templates
@@ -13,6 +14,22 @@ namespace Avalonia.Controls.Templates
public DataTemplates()
{
ResetBehavior = ResetBehavior.Remove;
+
+ Validate += ValidateDataTemplate;
+ }
+
+ private static void ValidateDataTemplate(IDataTemplate template)
+ {
+ var valid = template switch
+ {
+ ITypedDataTemplate typed => typed.DataType is not null,
+ _ => true
+ };
+
+ if (!valid)
+ {
+ throw new InvalidOperationException("DataTemplate inside of DataTemplates must have a DataType set. Set DataType property or use ItemTemplate with single template instead.");
+ }
}
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Templates/ITypedDataTemplate.cs b/src/Avalonia.Controls/Templates/ITypedDataTemplate.cs
new file mode 100644
index 0000000000..239dbd79f4
--- /dev/null
+++ b/src/Avalonia.Controls/Templates/ITypedDataTemplate.cs
@@ -0,0 +1,10 @@
+using System;
+using Avalonia.Metadata;
+
+namespace Avalonia.Controls.Templates;
+
+public interface ITypedDataTemplate : IDataTemplate
+{
+ [DataType]
+ Type? DataType { get; }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
index d2b24979cc..4da6b1b791 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
@@ -5,7 +5,7 @@ using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.Templates
{
- public class DataTemplate : IRecyclingDataTemplate
+ public class DataTemplate : IRecyclingDataTemplate, ITypedDataTemplate
{
[DataType]
public Type DataType { get; set; }
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
index 10061c3d48..04e8b0a9c0 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
@@ -9,7 +9,7 @@ using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.Templates
{
- public class TreeDataTemplate : ITreeDataTemplate
+ public class TreeDataTemplate : ITreeDataTemplate, ITypedDataTemplate
{
[DataType]
public Type DataType { get; set; }
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
index 7e721fd7b2..555a05638b 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
@@ -413,11 +413,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:TestDataContext'>
-
+
-
+
";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs
index 8188b212e1..affa292a7d 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs
@@ -74,18 +74,18 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
-
+
-
+
-
+
-
+
";
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
index 53881467e7..abbcf6c5a8 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
@@ -1,3 +1,4 @@
+using System;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.UnitTests;
@@ -132,5 +133,25 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Same(viewModel.Child.Child, canvas.DataContext);
}
}
+
+ [Fact]
+ public void DataTemplates_Without_Type_Should_Throw()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+";
+ Assert.Throws(() => (Window)AvaloniaRuntimeXamlLoader.Load(xaml));
+ }
+ }
}
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs
index 3fdac49f31..807b37517a 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
using (UnitTestApplication.Start(TestServices.MockPlatformWrapper))
{
- var xaml = "";
+ var xaml = "";
var templates = (DataTemplates)AvaloniaRuntimeXamlLoader.Load(xaml);
var template = (TreeDataTemplate)(templates.First());