Browse Source

Use ItemTemplate for ComboBox's selection box for controls (#21937)

* Add failing tests for SelectionBoxItem with a control

* Use ItemTemplate for ComboBox's selection box for controls

* Add failing selection box tests when properties change

* Update selection box when its template changes
pull/22067/head
Julien Lebosquain 3 weeks ago
committed by GitHub
parent
commit
a13617dfdf
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 45
      src/Avalonia.Controls/ComboBox.cs
  2. 2
      src/Avalonia.Controls/ItemsControl.cs
  3. 216
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

45
src/Avalonia.Controls/ComboBox.cs

@ -102,7 +102,7 @@ namespace Avalonia.Controls
if (template is not null) return template;
if(obj is ComboBox comboBox && template is null)
{
return comboBox.ItemTemplate;
return comboBox.GetEffectiveItemTemplate();
}
return template;
}
@ -425,6 +425,10 @@ namespace Avalonia.Controls
{
CoerceValue(SelectionBoxItemTemplateProperty);
}
else if (change.Property == SelectionBoxItemTemplateProperty)
{
UpdateSelectionBoxItem(SelectedItem);
}
else if (change.Property == IsEditableProperty && change.GetNewValue<bool>())
{
UpdateInputTextFromSelection(SelectedItem);
@ -444,6 +448,10 @@ namespace Avalonia.Controls
else if (change.Property == DisplayMemberBindingProperty)
{
HandleTextValueBindingValueChanged(null, change);
// The base handler invalidates the cached template: run it before coercing.
base.OnPropertyChanged(change);
CoerceValue(SelectionBoxItemTemplateProperty);
return;
}
else if (change.Property == TextSearch.TextBindingProperty)
{
@ -533,18 +541,24 @@ namespace Avalonia.Controls
private void UpdateSelectionBoxItem(object? item)
{
var contentControl = item as IContentControl;
if (contentControl != null)
if (item is IContentControl contentControl)
{
item = contentControl.Content;
}
var control = item as Control;
if (item is null)
{
SelectionBoxItem = null;
return;
}
if (control != null)
if (SelectionBoxItemTemplate is not null)
{
if (VisualRoot is object)
SelectionBoxItem = item;
}
else if (item is Control control)
{
if (VisualRoot is not null)
{
control.Measure(Size.Infinity);
@ -565,22 +579,7 @@ namespace Avalonia.Controls
}
else
{
if (item is not null && ItemTemplate is null && SelectionBoxItemTemplate is null && DisplayMemberBinding is { } binding)
{
var template = new FuncDataTemplate<object?>((_, _) =>
new TextBlock
{
[TextBlock.DataContextProperty] = item,
[!TextBlock.TextProperty] = binding,
});
var text = template.Build(item);
SelectionBoxItem = text;
}
else
{
SelectionBoxItem = item;
}
SelectionBoxItem = item;
}
}

2
src/Avalonia.Controls/ItemsControl.cs

@ -776,7 +776,7 @@ namespace Avalonia.Controls
LogicalChildren.RemoveAll(toRemove);
}
private IDataTemplate? GetEffectiveItemTemplate()
private protected IDataTemplate? GetEffectiveItemTemplate()
{
if (ItemTemplate is { } itemTemplate)
return itemTemplate;

216
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@ -702,6 +702,222 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(target.SelectionBoxItem);
}
[Fact]
public void ItemTemplate_Is_Applied_To_Control_Item_In_DropDown()
{
using var app = UnitTestApplication.Start(TestServices.StyledWindow);
var item = new Canvas();
var target = new ComboBox
{
Items = { item },
ItemTemplate = new FuncDataTemplate<object?>((x, _) => new TextBlock { Tag = x }),
SelectedIndex = 0
};
var window = new Window { Content = target };
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
target.IsDropDownOpen = true;
window.LayoutManager.ExecuteLayoutPass();
var container = Assert.IsType<ComboBoxItem>(target.ContainerFromIndex(0));
var textBlock = Assert.IsType<TextBlock>(container.Presenter?.Child);
Assert.Same(item, textBlock.Tag);
}
[Fact]
public void DisplayMemberBinding_Is_Applied_To_Control_Item_In_DropDown()
{
using var app = UnitTestApplication.Start(TestServices.StyledWindow);
var item = new Canvas { Tag = "foo" };
var target = new ComboBox
{
Items = { item },
DisplayMemberBinding = CompiledBinding.Create((Canvas c) => c.Tag),
SelectedIndex = 0
};
var window = new Window { Content = target };
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
target.IsDropDownOpen = true;
window.LayoutManager.ExecuteLayoutPass();
var container = Assert.IsType<ComboBoxItem>(target.ContainerFromIndex(0));
var textBlock = Assert.IsType<TextBlock>(container.Presenter?.Child);
Assert.Equal("foo", textBlock.Text);
}
[Fact]
public void ItemTemplate_Is_Applied_To_Control_Item_In_SelectionBox()
{
using var app = UnitTestApplication.Start(TestServices.StyledWindow);
var item = new Canvas();
var target = new ComboBox
{
Items = { item },
ItemTemplate = new FuncDataTemplate<object?>((x, _) => new TextBlock { Tag = x }),
SelectedIndex = 0
};
var window = new Window { Content = target };
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
var selectionBoxControl = target.FindDescendantOfType<ContentControl>(
false, c => c.ContentTemplate == target.SelectionBoxItemTemplate);
Assert.NotNull(selectionBoxControl);
var textBlock = Assert.IsType<TextBlock>(selectionBoxControl.Presenter?.Child);
Assert.Same(item, textBlock.Tag);
}
[Fact]
public void DisplayMemberBinding_Is_Applied_To_Control_Item_In_SelectionBox()
{
using var app = UnitTestApplication.Start(TestServices.StyledWindow);
var item = new Canvas { Tag = "foo" };
var target = new ComboBox
{
Items = { item },
DisplayMemberBinding = CompiledBinding.Create((Canvas c) => c.Tag),
SelectedIndex = 0
};
var window = new Window { Content = target };
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
var selectionBoxControl = target.FindDescendantOfType<ContentControl>(
false, c => c.ContentTemplate == target.SelectionBoxItemTemplate);
Assert.NotNull(selectionBoxControl);
var textBlock = Assert.IsType<TextBlock>(selectionBoxControl.Presenter?.Child);
Assert.Equal("foo", textBlock.Text);
}
[Fact]
public void SelectionBoxItemTemplate_Is_Applied_To_Control_Item_In_SelectionBox()
{
using var app = UnitTestApplication.Start(TestServices.StyledWindow);
var item = new Canvas();
var target = new ComboBox
{
Items = { item },
SelectionBoxItemTemplate = new FuncDataTemplate<object?>((x, _) => new TextBlock { Tag = x }),
SelectedIndex = 0
};
var window = new Window { Content = target };
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
var selectionBoxControl = target.FindDescendantOfType<ContentControl>(
false, c => c.ContentTemplate == target.SelectionBoxItemTemplate);
Assert.NotNull(selectionBoxControl);
var textBlock = Assert.IsType<TextBlock>(selectionBoxControl.Presenter?.Child);
Assert.Same(item, textBlock.Tag);
}
[Fact]
public void ItemTemplate_Is_Applied_To_Control_Item_In_SelectionBox_When_Changed()
{
using var app = UnitTestApplication.Start(TestServices.StyledWindow);
var item = new Canvas();
var target = new ComboBox { Items = { item }, SelectedIndex = 0 };
var window = new Window { Content = target };
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
AssertSelectionBoxItemIsVisualBrushOf(target, item);
target.ItemTemplate = new FuncDataTemplate<object?>((x, _) => new TextBlock { Tag = x });
window.LayoutManager.ExecuteLayoutPass();
var textBlock = Assert.IsType<TextBlock>(GetSelectionBoxControl(target).Presenter?.Child);
Assert.Same(item, textBlock.Tag);
target.ItemTemplate = null;
window.LayoutManager.ExecuteLayoutPass();
AssertSelectionBoxItemIsVisualBrushOf(target, item);
}
[Fact]
public void DisplayMemberBinding_Is_Applied_To_Control_Item_In_SelectionBox_When_Changed()
{
using var app = UnitTestApplication.Start(TestServices.StyledWindow);
var item = new Canvas { Tag = "foo" };
var target = new ComboBox { Items = { item }, SelectedIndex = 0 };
var window = new Window { Content = target };
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
AssertSelectionBoxItemIsVisualBrushOf(target, item);
target.DisplayMemberBinding = CompiledBinding.Create((Canvas c) => c.Tag);
window.LayoutManager.ExecuteLayoutPass();
var textBlock = Assert.IsType<TextBlock>(GetSelectionBoxControl(target).Presenter?.Child);
Assert.Equal("foo", textBlock.Text);
target.DisplayMemberBinding = null;
window.LayoutManager.ExecuteLayoutPass();
AssertSelectionBoxItemIsVisualBrushOf(target, item);
}
[Fact]
public void SelectionBoxItemTemplate_Is_Applied_To_Control_Item_In_SelectionBox_When_Changed()
{
using var app = UnitTestApplication.Start(TestServices.StyledWindow);
var item = new Canvas();
var target = new ComboBox { Items = { item }, SelectedIndex = 0 };
var window = new Window { Content = target };
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
AssertSelectionBoxItemIsVisualBrushOf(target, item);
target.SelectionBoxItemTemplate = new FuncDataTemplate<object?>((x, _) => new TextBlock { Tag = x });
window.LayoutManager.ExecuteLayoutPass();
var textBlock = Assert.IsType<TextBlock>(GetSelectionBoxControl(target).Presenter?.Child);
Assert.Same(item, textBlock.Tag);
target.SelectionBoxItemTemplate = null;
window.LayoutManager.ExecuteLayoutPass();
AssertSelectionBoxItemIsVisualBrushOf(target, item);
}
private static ContentControl GetSelectionBoxControl(ComboBox target)
{
var selectionBoxControl = target.FindDescendantOfType<ContentControl>(
false, c => c.ContentTemplate == target.SelectionBoxItemTemplate);
Assert.NotNull(selectionBoxControl);
return selectionBoxControl;
}
private static void AssertSelectionBoxItemIsVisualBrushOf(ComboBox target, Control item)
{
var rectangle = Assert.IsType<Rectangle>(target.SelectionBoxItem);
var visualBrush = Assert.IsType<VisualBrush>(rectangle.Fill);
Assert.Same(item, visualBrush.Visual);
}
private sealed record Item(string Value, string Display);
[Fact]

Loading…
Cancel
Save