From edb66a77201f2f5bc8a7badb51f59278aa1ffc5b Mon Sep 17 00:00:00 2001 From: Dong Bin <14807942+rabbitism@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:09:32 +0800 Subject: [PATCH] ComboBox: support different DataTemplate for selected item. (#15420) * feat: ComboBox: support different DataTemplate for selected item. * feat: use coerce instead of multi binding. * test: add multiple unit tests. --- .../ControlCatalog/Pages/ComboBoxPage.xaml | 29 +++++++-- src/Avalonia.Controls/ComboBox.cs | 36 ++++++++++- .../Controls/ComboBox.xaml | 5 +- .../Controls/ComboBox.xaml | 3 +- .../ComboBoxTests.cs | 64 +++++++++++++++++++ 5 files changed, 127 insertions(+), 10 deletions(-) diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml b/samples/ControlCatalog/Pages/ComboBoxPage.xaml index eca2c4762c8..ab347ac30c4 100644 --- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml @@ -12,8 +12,8 @@ + WrapSelection Inline Item 4 - + + + + + + + + + + + + + + + + + @@ -114,9 +136,6 @@ - - WrapSelection - diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 78bb191e93b..f1cc84f7a4b 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -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; @@ -71,6 +73,23 @@ public class ComboBox : SelectingItemsControl /// public static readonly StyledProperty VerticalContentAlignmentProperty = ContentControl.VerticalContentAlignmentProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty SelectionBoxItemTemplateProperty = + AvaloniaProperty.Register( + 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; @@ -159,6 +178,16 @@ public VerticalAlignment VerticalContentAlignment set => SetValue(VerticalContentAlignmentProperty, value); } + /// + /// Gets or sets the DataTemplate used to display the selected item. This has a higher priority than if set. + /// + [InheritDataTypeFromItems(nameof(ItemsSource))] + public IDataTemplate? SelectionBoxItemTemplate + { + get => GetValue(SelectionBoxItemTemplateProperty); + set => SetValue(SelectionBoxItemTemplateProperty, value); + } + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); @@ -322,7 +351,10 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang { PseudoClasses.Set(pcDropdownOpen, change.GetNewValue()); } - + else if (change.Property == ItemTemplateProperty) + { + CoerceValue(SelectionBoxItemTemplateProperty); + } base.OnPropertyChanged(change); } @@ -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((_, _) => new TextBlock diff --git a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml index 5efe6f17905..d3c8417875a 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml @@ -84,11 +84,12 @@ IsVisible="{TemplateBinding SelectionBoxItem, Converter={x:Static ObjectConverters.IsNull}}" /> + ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" + VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"> + + ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"> + ((x, _) => new TextBlock { Text = x + "!" }); + IDataTemplate selectionBoxItemTemplate = new FuncDataTemplate((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((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((x, _) => new TextBlock { Text = x + "!" }); + IDataTemplate selectionBoxItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Text = x }); + IDataTemplate itemTemplate2 = new FuncDataTemplate((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((x, _) => new TextBlock { Text = x + "!" }); + IDataTemplate selectionBoxItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Text = x }); + IDataTemplate itemTemplate2 = new FuncDataTemplate((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); + } } }