Browse Source

Allow setting item container properties in styles.

Allow overriding the default behavior of item containers in styles or in `ItemContainerTheme`. To do this, use `SetCurrentValue` to set the properties, only if the properties are not already set (i.e. from a style). This also requires us to clear the current value when the container is cleared (styles won't be affected as `AvaloniaObject.ClearValue` only clears local or `SetCurrentValue` values).
pull/11040/head
Steven Kirk 3 years ago
parent
commit
d37de0b634
  1. 67
      src/Avalonia.Controls/ItemsControl.cs
  2. 32
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  3. 95
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

67
src/Avalonia.Controls/ItemsControl.cs

@ -378,48 +378,48 @@ namespace Avalonia.Controls
if (container is HeaderedContentControl hcc) if (container is HeaderedContentControl hcc)
{ {
hcc.Content = item; SetIfUnset(hcc, HeaderedContentControl.ContentProperty, item);
if (item is IHeadered headered) if (item is IHeadered headered)
hcc.Header = headered.Header; SetIfUnset(hcc, HeaderedContentControl.HeaderProperty, headered.Header);
else if (item is not Visual) else if (item is not Visual)
hcc.Header = item; SetIfUnset(hcc, HeaderedContentControl.HeaderProperty, item);
if (itemTemplate is not null) if (itemTemplate is not null)
hcc.HeaderTemplate = itemTemplate; SetIfUnset(hcc, HeaderedContentControl.HeaderTemplateProperty, itemTemplate);
} }
else if (container is ContentControl cc) else if (container is ContentControl cc)
{ {
cc.Content = item; SetIfUnset(cc, ContentControl.ContentProperty, item);
if (itemTemplate is not null) if (itemTemplate is not null)
cc.ContentTemplate = itemTemplate; SetIfUnset(cc, ContentControl.ContentTemplateProperty, itemTemplate);
} }
else if (container is ContentPresenter p) else if (container is ContentPresenter p)
{ {
p.Content = item; SetIfUnset(p, ContentPresenter.ContentProperty, item);
if (itemTemplate is not null) if (itemTemplate is not null)
p.ContentTemplate = itemTemplate; SetIfUnset(p, ContentPresenter.ContentTemplateProperty, itemTemplate);
} }
else if (container is ItemsControl ic) else if (container is ItemsControl ic)
{ {
if (itemTemplate is not null) if (itemTemplate is not null)
ic.ItemTemplate = itemTemplate; SetIfUnset(ic, ItemTemplateProperty, itemTemplate);
if (ItemContainerTheme is { } ict && !ict.IsSet(ItemContainerThemeProperty)) if (ItemContainerTheme is { } ict)
ic.ItemContainerTheme = ict; SetIfUnset(ic, ItemContainerThemeProperty, ict);
} }
// These conditions are separate because HeaderedItemsControl and // These conditions are separate because HeaderedItemsControl and
// HeaderedSelectingItemsControl also need to run the ItemsControl preparation. // HeaderedSelectingItemsControl also need to run the ItemsControl preparation.
if (container is HeaderedItemsControl hic) if (container is HeaderedItemsControl hic)
{ {
hic.Header = item; SetIfUnset(hic, HeaderedItemsControl.HeaderProperty, item);
hic.HeaderTemplate = itemTemplate; SetIfUnset(hic, HeaderedItemsControl.HeaderTemplateProperty, itemTemplate);
hic.PrepareItemContainer(this); hic.PrepareItemContainer(this);
} }
else if (container is HeaderedSelectingItemsControl hsic) else if (container is HeaderedSelectingItemsControl hsic)
{ {
hsic.Header = item; SetIfUnset(hsic, HeaderedSelectingItemsControl.HeaderProperty, item);
hsic.HeaderTemplate = itemTemplate; SetIfUnset(hsic, HeaderedSelectingItemsControl.HeaderTemplateProperty, itemTemplate);
hsic.PrepareItemContainer(this); hsic.PrepareItemContainer(this);
} }
} }
@ -458,30 +458,35 @@ namespace Avalonia.Controls
{ {
if (container is HeaderedContentControl hcc) if (container is HeaderedContentControl hcc)
{ {
if (hcc.Content is Control) hcc.ClearValue(HeaderedContentControl.ContentProperty);
hcc.Content = null; hcc.ClearValue(HeaderedContentControl.HeaderProperty);
if (hcc.Header is Control) hcc.ClearValue(HeaderedContentControl.HeaderTemplateProperty);
hcc.Header = null;
} }
else if (container is ContentControl cc) else if (container is ContentControl cc)
{ {
if (cc.Content is Control) cc.ClearValue(ContentControl.ContentProperty);
cc.Content = null; cc.ClearValue(ContentControl.ContentTemplateProperty);
} }
else if (container is ContentPresenter p) else if (container is ContentPresenter p)
{ {
if (p.Content is Control) p.ClearValue(ContentPresenter.ContentProperty);
p.Content = null; 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.ClearValue(HeaderedItemsControl.HeaderProperty);
hic.Header = null; hic.ClearValue(HeaderedItemsControl.HeaderTemplateProperty);
} }
else if (container is HeaderedSelectingItemsControl hsic) else if (container is HeaderedSelectingItemsControl hsic)
{ {
if (hsic.Header is Control) hsic.ClearValue(HeaderedSelectingItemsControl.HeaderProperty);
hsic.Header = null; hsic.ClearValue(HeaderedSelectingItemsControl.HeaderTemplateProperty);
} }
// Feels like we should be clearing the HeaderedItemsControl.Items binding here, but looking at // Feels like we should be clearing the HeaderedItemsControl.Items binding here, but looking at
@ -707,6 +712,12 @@ namespace Avalonia.Controls
LogicalChildren.AddRange(toAdd); LogicalChildren.AddRange(toAdd);
} }
private void SetIfUnset<T>(AvaloniaObject target, StyledProperty<T> property, T value)
{
if (!target.IsSet(property))
target.SetCurrentValue(property, value);
}
private void RemoveControlItemsFromLogicalChildren(IEnumerable? items) private void RemoveControlItemsFromLogicalChildren(IEnumerable? items)
{ {
if (items is null) if (items is null)

32
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -554,6 +554,36 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { "Bar" }, target.Selection.SelectedItems); 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<ListBoxItem>().ToList();
Assert.Equal(2, containers.Count);
Assert.Equal("Foo", containers[0].Content);
Assert.Equal("Bar", containers[1].Content);
}
}
private static FuncControlTemplate ListBoxTemplate() private static FuncControlTemplate ListBoxTemplate()
{ {
return new FuncControlTemplate<ListBox>((parent, scope) => return new FuncControlTemplate<ListBox>((parent, scope) =>
@ -918,6 +948,8 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, raised); Assert.Equal(1, raised);
} }
private record ItemViewModel(string Caption);
private class ResettingCollection : List<string>, INotifyCollectionChanged private class ResettingCollection : List<string>, INotifyCollectionChanged
{ {
public ResettingCollection(int itemCount) public ResettingCollection(int itemCount)

95
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@ -1,16 +1,16 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Linq;
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Collections;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq; using Moq;
using Xunit; using Xunit;
@ -36,7 +36,6 @@ namespace Avalonia.Controls.UnitTests
Assert.False(target.Focusable); Assert.False(target.Focusable);
} }
[Fact] [Fact]
public void MenuItem_Is_Disabled_When_Command_Is_Enabled_But_IsEnabled_Is_False() 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<MenuItem>())
{
Setters =
{
new Setter(MenuItem.HeaderProperty, new Binding("Header")),
new Setter(MenuItem.ItemsSourceProperty, new Binding("Children")),
}
}
}
};
var root = new TestRoot(true, target);
root.LayoutManager.ExecuteInitialLayoutPass();
var children = target.GetRealizedContainers().Cast<MenuItem>().ToList();
Assert.Equal(2, children.Count);
Assert.Equal("Foo", children[0].Header);
Assert.Equal("Bar", children[1].Header);
Assert.Same(items[0].Children, children[0].ItemsSource);
}
[Fact]
public void Header_And_ItemsSource_Can_Be_Bound_In_ItemContainerTheme()
{
using var app = Application();
var items = new[]
{
new MenuViewModel("Foo")
{
Children = new[]
{
new MenuViewModel("FooChild"),
},
},
new MenuViewModel("Bar"),
};
var target = new Menu
{
ItemsSource = items,
ItemContainerTheme = new ControlTheme(typeof(MenuItem))
{
Setters =
{
new Setter(MenuItem.HeaderProperty, new Binding("Header")),
new Setter(MenuItem.ItemsSourceProperty, new Binding("Children")),
}
}
};
var root = new TestRoot(true, target);
root.LayoutManager.ExecuteInitialLayoutPass();
var children = target.GetRealizedContainers().Cast<MenuItem>().ToList();
Assert.Equal(2, children.Count);
Assert.Equal("Foo", children[0].Header);
Assert.Equal("Bar", children[1].Header);
Assert.Same(items[0].Children, children[0].ItemsSource);
}
private IDisposable Application() private IDisposable Application()
{ {
var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100)); var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100));
@ -447,6 +527,9 @@ namespace Avalonia.Controls.UnitTests
public void RaiseCanExecuteChanged() => _canExecuteChanged?.Invoke(this, EventArgs.Empty); public void RaiseCanExecuteChanged() => _canExecuteChanged?.Invoke(this, EventArgs.Empty);
} }
private record MenuViewModel(string Header); private record MenuViewModel(string Header)
{
public IList<MenuViewModel> Children { get; set;}
}
} }
} }

Loading…
Cancel
Save