Skip to content

Commit

Permalink
ComboBox: support different DataTemplate for selected item. (#15420)
Browse files Browse the repository at this point in the history
* feat: ComboBox: support different DataTemplate for selected item.

* feat: use coerce instead of multi binding.

* test: add multiple unit tests.
  • Loading branch information
rabbitism authored Apr 22, 2024
1 parent 7a7ab8e commit edb66a7
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 10 deletions.
29 changes: 24 additions & 5 deletions samples/ControlCatalog/Pages/ComboBoxPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
<StackPanel
Margin="0,16,0,0"
HorizontalAlignment="Center"
Orientation="Horizontal"
Spacing="8">
<CheckBox IsChecked="{Binding WrapSelection}">WrapSelection</CheckBox>
<WrapPanel
MaxWidth="660"
Margin="0,16,0,0"
Expand Down Expand Up @@ -99,9 +99,31 @@
<ComboBoxItem>Inline Item 4</ComboBoxItem>
</ComboBox>

<ComboBox WrapSelection="{Binding WrapSelection}" ItemsSource="{Binding Values}" DisplayMemberBinding="{Binding Name}">
<ComboBox
WrapSelection="{Binding WrapSelection}"
ItemsSource="{Binding Values}"
DisplayMemberBinding="{Binding Name}">

</ComboBox>

<ComboBox
WrapSelection="{Binding WrapSelection}"
PlaceholderText="Different Template"
ItemsSource="{Binding Values}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text="{Binding Id}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.SelectionBoxItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Foreground="Red"></TextBlock>
</DataTemplate>
</ComboBox.SelectionBoxItemTemplate>
</ComboBox>

<ComboBox WrapSelection="{Binding WrapSelection}" ItemsSource="{Binding Values}" >
<ComboBox.ItemTemplate>
Expand All @@ -114,9 +136,6 @@
</ComboBox.ItemTemplate>
</ComboBox>
</WrapPanel>

<CheckBox IsChecked="{Binding WrapSelection}">WrapSelection</CheckBox>

</StackPanel>
</StackPanel>
</UserControl>
36 changes: 34 additions & 2 deletions src/Avalonia.Controls/ComboBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Reactive;
using Avalonia.VisualTree;

Expand Down Expand Up @@ -71,6 +73,23 @@ public class ComboBox : SelectingItemsControl
/// </summary>
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>();

/// <summary>
/// Defines the <see cref="SelectionBoxItemTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate?> SelectionBoxItemTemplateProperty =
AvaloniaProperty.Register<ComboBox, IDataTemplate?>(
nameof(SelectionBoxItemTemplate), defaultBindingMode: BindingMode.TwoWay, coerce: CoerceSelectionBoxItemTemplate);

private static IDataTemplate? CoerceSelectionBoxItemTemplate(AvaloniaObject obj, IDataTemplate? template)
{
if (template is not null) return template;
if(obj is ComboBox comboBox && template is null)
{
return comboBox.ItemTemplate;
}
return template;
}

private Popup? _popup;
private object? _selectionBoxItem;
Expand Down Expand Up @@ -159,6 +178,16 @@ public VerticalAlignment VerticalContentAlignment
set => SetValue(VerticalContentAlignmentProperty, value);
}

/// <summary>
/// Gets or sets the DataTemplate used to display the selected item. This has a higher priority than <see cref="ItemsControl.ItemTemplate"/> if set.
/// </summary>
[InheritDataTypeFromItems(nameof(ItemsSource))]
public IDataTemplate? SelectionBoxItemTemplate
{
get => GetValue(SelectionBoxItemTemplateProperty);
set => SetValue(SelectionBoxItemTemplateProperty, value);
}

protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
Expand Down Expand Up @@ -322,7 +351,10 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
{
PseudoClasses.Set(pcDropdownOpen, change.GetNewValue<bool>());
}

else if (change.Property == ItemTemplateProperty)
{
CoerceValue(SelectionBoxItemTemplateProperty);
}
base.OnPropertyChanged(change);
}

Expand Down Expand Up @@ -433,7 +465,7 @@ private void UpdateSelectionBoxItem(object? item)
}
else
{
if(ItemTemplate is null && DisplayMemberBinding is { } binding)
if(ItemTemplate is null && SelectionBoxItemTemplate is null && DisplayMemberBinding is { } binding)
{
var template = new FuncDataTemplate<object?>((_, _) =>
new TextBlock
Expand Down
5 changes: 3 additions & 2 deletions src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,12 @@
IsVisible="{TemplateBinding SelectionBoxItem, Converter={x:Static ObjectConverters.IsNull}}" />
<ContentControl x:Name="ContentPresenter"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding ItemTemplate}"
Grid.Column="0"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
</ContentControl>

<Border x:Name="DropDownOverlay"
Grid.Column="1"
Expand Down
3 changes: 2 additions & 1 deletion src/Avalonia.Themes.Simple/Controls/ComboBox.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding ItemTemplate}" />
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}">
</ContentControl>
<ToggleButton Name="toggle"
Grid.Column="1"
Background="Transparent"
Expand Down
64 changes: 64 additions & 0 deletions tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -453,5 +453,69 @@ public void FlowDirection_Of_RectangleContent_Updated_After_OpenPopup()
Assert.Equal(FlowDirection.RightToLeft, rectangle.FlowDirection);
}
}

[Fact]
public void SelectionBoxItemTemplate_Overrides_ItemTemplate()
{
IDataTemplate itemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x + "!" });
IDataTemplate selectionBoxItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x });
var target = new ComboBox
{
ItemsSource = new []{ "Foo" },
SelectionBoxItemTemplate = selectionBoxItemTemplate,
ItemTemplate = itemTemplate,
};

Assert.Equal(selectionBoxItemTemplate, target.SelectionBoxItemTemplate);
}

[Fact]
public void SelectionBoxItemTemplate_Inherits_From_ItemTemplate_When_NotSet()
{
IDataTemplate itemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x + "!" });
var target = new ComboBox
{
ItemsSource = new []{ "Foo" },
ItemTemplate = itemTemplate,
};

Assert.Equal(itemTemplate, target.SelectionBoxItemTemplate);
}

[Fact]
public void SelectionBoxItemTemplate_Overrides_ItemTemplate_After_ItemTemplate_Changed()
{
IDataTemplate itemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x + "!" });
IDataTemplate selectionBoxItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x });
IDataTemplate itemTemplate2 = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x + "?" });
var target = new ComboBox
{
ItemsSource = new[] { "Foo" },
SelectionBoxItemTemplate = selectionBoxItemTemplate,
ItemTemplate = itemTemplate,
};

Assert.Equal(selectionBoxItemTemplate, target.SelectionBoxItemTemplate);

target.ItemTemplate = itemTemplate2;

Assert.Equal(selectionBoxItemTemplate, target.SelectionBoxItemTemplate);
}

[Fact]
public void SelectionBoxItemTemplate_Inherits_From_ItemTemplate_When_ItemTemplate_Changed()
{
IDataTemplate itemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x + "!" });
IDataTemplate selectionBoxItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x });
IDataTemplate itemTemplate2 = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x + "?" });
var target = new ComboBox { ItemsSource = new[] { "Foo" }, ItemTemplate = itemTemplate, };

Assert.Equal(itemTemplate, target.SelectionBoxItemTemplate);

target.ItemTemplate = itemTemplate2;
target.SelectionBoxItemTemplate = null;

Assert.Equal(itemTemplate2, target.SelectionBoxItemTemplate);
}
}
}

0 comments on commit edb66a7

Please sign in to comment.