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.
This commit is contained in:
Dong Bin 2024-04-22 15:09:32 +08:00 коммит произвёл GitHub
Родитель 7a7ab8e5f8
Коммит edb66a7720
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
5 изменённых файлов: 127 добавлений и 10 удалений

Просмотреть файл

@ -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"
@ -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>
@ -114,9 +136,6 @@
</ComboBox.ItemTemplate>
</ComboBox>
</WrapPanel>
<CheckBox IsChecked="{Binding WrapSelection}">WrapSelection</CheckBox>
</StackPanel>
</StackPanel>
</UserControl>

Просмотреть файл

@ -5,10 +5,12 @@ using Avalonia.Controls.Metadata;
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 @@ namespace Avalonia.Controls
/// </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;
@ -159,6 +178,16 @@ namespace Avalonia.Controls
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);
@ -322,7 +351,10 @@ namespace Avalonia.Controls
{
PseudoClasses.Set(pcDropdownOpen, change.GetNewValue<bool>());
}
else if (change.Property == ItemTemplateProperty)
{
CoerceValue(SelectionBoxItemTemplateProperty);
}
base.OnPropertyChanged(change);
}
@ -433,7 +465,7 @@ namespace Avalonia.Controls
}
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

Просмотреть файл

@ -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"

Просмотреть файл

@ -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"

Просмотреть файл

@ -453,5 +453,69 @@ namespace Avalonia.Controls.UnitTests
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);
}
}
}