diff --git a/.ncrunch/SafeAreaDemo.Android.v3.ncrunchproject b/.ncrunch/SafeAreaDemo.Android.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/SafeAreaDemo.Android.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/SafeAreaDemo.Desktop.v3.ncrunchproject b/.ncrunch/SafeAreaDemo.Desktop.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/SafeAreaDemo.Desktop.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/SafeAreaDemo.iOS.v3.ncrunchproject b/.ncrunch/SafeAreaDemo.iOS.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/SafeAreaDemo.iOS.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/SafeAreaDemo.v3.ncrunchproject b/.ncrunch/SafeAreaDemo.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/SafeAreaDemo.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index 60b0f8b193..1c62de9bed 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -378,48 +378,48 @@ namespace Avalonia.Controls
if (container is HeaderedContentControl hcc)
{
- hcc.Content = item;
+ SetIfUnset(hcc, HeaderedContentControl.ContentProperty, item);
if (item is IHeadered headered)
- hcc.Header = headered.Header;
+ SetIfUnset(hcc, HeaderedContentControl.HeaderProperty, headered.Header);
else if (item is not Visual)
- hcc.Header = item;
+ SetIfUnset(hcc, HeaderedContentControl.HeaderProperty, item);
if (itemTemplate is not null)
- hcc.HeaderTemplate = itemTemplate;
+ SetIfUnset(hcc, HeaderedContentControl.HeaderTemplateProperty, itemTemplate);
}
else if (container is ContentControl cc)
{
- cc.Content = item;
+ SetIfUnset(cc, ContentControl.ContentProperty, item);
if (itemTemplate is not null)
- cc.ContentTemplate = itemTemplate;
+ SetIfUnset(cc, ContentControl.ContentTemplateProperty, itemTemplate);
}
else if (container is ContentPresenter p)
{
- p.Content = item;
+ SetIfUnset(p, ContentPresenter.ContentProperty, item);
if (itemTemplate is not null)
- p.ContentTemplate = itemTemplate;
+ SetIfUnset(p, ContentPresenter.ContentTemplateProperty, itemTemplate);
}
else if (container is ItemsControl ic)
{
if (itemTemplate is not null)
- ic.ItemTemplate = itemTemplate;
- if (ItemContainerTheme is { } ict && !ict.IsSet(ItemContainerThemeProperty))
- ic.ItemContainerTheme = ict;
+ SetIfUnset(ic, ItemTemplateProperty, itemTemplate);
+ if (ItemContainerTheme is { } ict)
+ SetIfUnset(ic, ItemContainerThemeProperty, ict);
}
// These conditions are separate because HeaderedItemsControl and
// HeaderedSelectingItemsControl also need to run the ItemsControl preparation.
if (container is HeaderedItemsControl hic)
{
- hic.Header = item;
- hic.HeaderTemplate = itemTemplate;
+ SetIfUnset(hic, HeaderedItemsControl.HeaderProperty, item);
+ SetIfUnset(hic, HeaderedItemsControl.HeaderTemplateProperty, itemTemplate);
hic.PrepareItemContainer(this);
}
else if (container is HeaderedSelectingItemsControl hsic)
{
- hsic.Header = item;
- hsic.HeaderTemplate = itemTemplate;
+ SetIfUnset(hsic, HeaderedSelectingItemsControl.HeaderProperty, item);
+ SetIfUnset(hsic, HeaderedSelectingItemsControl.HeaderTemplateProperty, itemTemplate);
hsic.PrepareItemContainer(this);
}
}
@@ -458,30 +458,35 @@ namespace Avalonia.Controls
{
if (container is HeaderedContentControl hcc)
{
- if (hcc.Content is Control)
- hcc.Content = null;
- if (hcc.Header is Control)
- hcc.Header = null;
+ hcc.ClearValue(HeaderedContentControl.ContentProperty);
+ hcc.ClearValue(HeaderedContentControl.HeaderProperty);
+ hcc.ClearValue(HeaderedContentControl.HeaderTemplateProperty);
}
else if (container is ContentControl cc)
{
- if (cc.Content is Control)
- cc.Content = null;
+ cc.ClearValue(ContentControl.ContentProperty);
+ cc.ClearValue(ContentControl.ContentTemplateProperty);
}
else if (container is ContentPresenter p)
{
- if (p.Content is Control)
- p.Content = null;
+ p.ClearValue(ContentPresenter.ContentProperty);
+ p.ClearValue(ContentPresenter.ContentTemplateProperty);
}
- else if (container is HeaderedItemsControl hic)
+ else if (container is ItemsControl ic)
+ {
+ ic.ClearValue(ItemTemplateProperty);
+ ic.ClearValue(ItemContainerThemeProperty);
+ }
+
+ if (container is HeaderedItemsControl hic)
{
- if (hic.Header is Control)
- hic.Header = null;
+ hic.ClearValue(HeaderedItemsControl.HeaderProperty);
+ hic.ClearValue(HeaderedItemsControl.HeaderTemplateProperty);
}
else if (container is HeaderedSelectingItemsControl hsic)
{
- if (hsic.Header is Control)
- hsic.Header = null;
+ hsic.ClearValue(HeaderedSelectingItemsControl.HeaderProperty);
+ hsic.ClearValue(HeaderedSelectingItemsControl.HeaderTemplateProperty);
}
// Feels like we should be clearing the HeaderedItemsControl.Items binding here, but looking at
@@ -707,6 +712,12 @@ namespace Avalonia.Controls
LogicalChildren.AddRange(toAdd);
}
+ private void SetIfUnset(AvaloniaObject target, StyledProperty property, T value)
+ {
+ if (!target.IsSet(property))
+ target.SetCurrentValue(property, value);
+ }
+
private void RemoveControlItemsFromLogicalChildren(IEnumerable? items)
{
if (items is null)
diff --git a/src/Avalonia.Controls/Templates/FuncControlTemplate.cs b/src/Avalonia.Controls/Templates/FuncControlTemplate.cs
index 64a883e88c..895ce53907 100644
--- a/src/Avalonia.Controls/Templates/FuncControlTemplate.cs
+++ b/src/Avalonia.Controls/Templates/FuncControlTemplate.cs
@@ -18,10 +18,10 @@ namespace Avalonia.Controls.Templates
{
}
- public new ControlTemplateResult Build(TemplatedControl param)
+ public new TemplateResult Build(TemplatedControl param)
{
var (control, scope) = BuildWithNameScope(param);
- return new ControlTemplateResult(control, scope);
+ return new(control, scope);
}
}
}
diff --git a/src/Avalonia.Controls/Templates/IControlTemplate.cs b/src/Avalonia.Controls/Templates/IControlTemplate.cs
index 38ad6561ab..c3f9c9e8aa 100644
--- a/src/Avalonia.Controls/Templates/IControlTemplate.cs
+++ b/src/Avalonia.Controls/Templates/IControlTemplate.cs
@@ -5,23 +5,7 @@ namespace Avalonia.Controls.Templates
///
/// Interface representing a template used to build a .
///
- public interface IControlTemplate : ITemplate
+ public interface IControlTemplate : ITemplate?>
{
}
-
- public class ControlTemplateResult : TemplateResult
- {
- public Control Control { get; }
-
- public ControlTemplateResult(Control control, INameScope nameScope) : base(control, nameScope)
- {
- Control = control;
- }
-
- public new void Deconstruct(out Control control, out INameScope scope)
- {
- control = Control;
- scope = NameScope;
- }
- }
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs
index 4bbdda31d8..b94eccf7c0 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Metadata;
@@ -13,6 +14,6 @@ namespace Avalonia.Markup.Xaml.Templates
public Type? TargetType { get; set; }
- public ControlTemplateResult? Build(TemplatedControl control) => TemplateContent.Load(Content);
+ public TemplateResult? Build(TemplatedControl control) => TemplateContent.Load(Content);
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
index 89b0468c6e..b45898d8bd 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
@@ -30,7 +30,7 @@ namespace Avalonia.Markup.Xaml.Templates
public Control? Build(object? data, Control? existing)
{
- return existing ?? TemplateContent.Load(Content)?.Control;
+ return existing ?? TemplateContent.Load(Content)?.Result;
}
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
index c228a58990..f31a693e72 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Markup.Xaml.Templates
[TemplateContent]
public object? Content { get; set; }
- public Panel? Build() => (Panel?)TemplateContent.Load(Content)?.Control;
+ public Panel? Build() => (Panel?)TemplateContent.Load(Content)?.Result;
object? ITemplate.Build() => Build();
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
index 62febebc8c..5999a8021e 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Markup.Xaml.Templates
[TemplateContent]
public object? Content { get; set; }
- public Control? Build() => TemplateContent.Load(Content)?.Control;
+ public Control? Build() => TemplateContent.Load(Content)?.Result;
object? ITemplate.Build() => Build();
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
index 08e897c514..504478f9b3 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
@@ -1,15 +1,16 @@
using System;
+using Avalonia.Controls;
using Avalonia.Controls.Templates;
namespace Avalonia.Markup.Xaml.Templates
{
public static class TemplateContent
{
- public static ControlTemplateResult? Load(object? templateContent)
+ public static TemplateResult? Load(object? templateContent)
{
if (templateContent is Func direct)
{
- return (ControlTemplateResult?)direct(null);
+ return (TemplateResult?)direct(null);
}
if (templateContent is null)
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
index a5b308523f..98c3b61c9f 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
@@ -54,7 +54,7 @@ namespace Avalonia.Markup.Xaml.Templates
public Control? Build(object? data)
{
- var visualTreeForItem = TemplateContent.Load(Content)?.Control;
+ var visualTreeForItem = TemplateContent.Load(Content)?.Result;
if (visualTreeForItem != null)
{
visualTreeForItem.DataContext = data;
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
index ba96ac15b3..0cc7cc5468 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
@@ -35,7 +35,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
scope.Complete();
if(typeof(T) == typeof(Control))
- return new ControlTemplateResult((Control)obj, scope);
+ return new TemplateResult((Control)obj, scope);
return new TemplateResult((T)obj, scope);
};
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
index 7a227a48ab..84eed5ec82 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
@@ -554,6 +554,36 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { "Bar" }, target.Selection.SelectedItems);
}
+ [Fact]
+ public void Content_Can_Be_Bound_In_ItemContainerTheme()
+ {
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
+ {
+ var items = new[] { new ItemViewModel("Foo"), new ItemViewModel("Bar") };
+ var theme = new ControlTheme(typeof(ListBoxItem))
+ {
+ Setters =
+ {
+ new Setter(ListBoxItem.ContentProperty, new Binding("Caption")),
+ }
+ };
+
+ var target = new ListBox
+ {
+ Template = ListBoxTemplate(),
+ ItemsSource = items,
+ ItemContainerTheme = theme,
+ };
+
+ Prepare(target);
+
+ var containers = target.GetRealizedContainers().Cast().ToList();
+ Assert.Equal(2, containers.Count);
+ Assert.Equal("Foo", containers[0].Content);
+ Assert.Equal("Bar", containers[1].Content);
+ }
+ }
+
private static FuncControlTemplate ListBoxTemplate()
{
return new FuncControlTemplate((parent, scope) =>
@@ -918,6 +948,8 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, raised);
}
+ private record ItemViewModel(string Caption);
+
private class ResettingCollection : List, INotifyCollectionChanged
{
public ResettingCollection(int itemCount)
diff --git a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
index bd59183e92..08aedceac3 100644
--- a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
@@ -1,16 +1,16 @@
using System;
+using System.Collections;
using System.Collections.Generic;
-using System.Text;
+using System.Linq;
using System.Windows.Input;
-using Avalonia.Collections;
using Avalonia.Controls.Presenters;
-using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
+using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Platform;
+using Avalonia.Styling;
using Avalonia.UnitTests;
-using Avalonia.VisualTree;
using Moq;
using Xunit;
@@ -36,7 +36,6 @@ namespace Avalonia.Controls.UnitTests
Assert.False(target.Focusable);
}
-
[Fact]
public void MenuItem_Is_Disabled_When_Command_Is_Enabled_But_IsEnabled_Is_False()
{
@@ -393,6 +392,87 @@ namespace Avalonia.Controls.UnitTests
}
}
+ [Fact]
+ public void Header_And_ItemsSource_Can_Be_Bound_In_Style()
+ {
+ using var app = Application();
+ var items = new[]
+ {
+ new MenuViewModel("Foo")
+ {
+ Children = new[]
+ {
+ new MenuViewModel("FooChild"),
+ },
+ },
+ new MenuViewModel("Bar"),
+ };
+
+ var target = new Menu
+ {
+ ItemsSource = items,
+ Styles =
+ {
+ new Style(x => x.OfType