Browse Source

Merge pull request #8922 from timunie/feature/DisplayMemberBinding

Add DisplayMemberBinding to ItemsControl and derived items
pull/9544/head
Max Katz 3 years ago
committed by GitHub
parent
commit
ee487bf4cf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  2. 8
      samples/ControlCatalog/Pages/TabControlPage.xaml
  3. 36
      samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
  4. 6
      src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
  5. 13
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  6. 12
      src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
  7. 7
      src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs
  8. 10
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  9. 19
      src/Avalonia.Controls/ItemsControl.cs
  10. 17
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  11. 17
      src/Avalonia.Controls/TabControl.cs
  12. 20
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

1
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -32,6 +32,7 @@
</StackPanel>
<ListBox Items="{Binding Items}"
Selection="{Binding Selection}"
DisplayMemberBinding="{Binding (viewModels:ItemModel).ID, StringFormat='{}Item {0:N0}'}"
AutoScrollToSelectedItem="{Binding AutoScrollToSelectedItem}"
SelectionMode="{Binding SelectionMode^}"
WrapSelection="{Binding WrapSelection}"/>

8
samples/ControlCatalog/Pages/TabControlPage.xaml

@ -53,14 +53,8 @@
<TabControl
Items="{Binding Tabs}"
Margin="0 16"
HeaderDisplayMemberBinding="{Binding Header, x:DataType=viewModels:TabControlPageViewModelItem}"
TabStripPlacement="{Binding TabPlacement}">
<TabControl.ItemTemplate>
<DataTemplate x:DataType="viewModels:TabControlPageViewModelItem">
<TextBlock
Text="{Binding Header}">
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate x:DataType="viewModels:TabControlPageViewModelItem">
<StackPanel Orientation="Vertical" Spacing="8">

36
samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs

@ -4,6 +4,7 @@ using System.Linq;
using System.Reactive;
using Avalonia.Controls;
using Avalonia.Controls.Selection;
using ControlCatalog.Pages;
using MiniMvvm;
namespace ControlCatalog.ViewModels
@ -20,9 +21,9 @@ namespace ControlCatalog.ViewModels
public ListBoxPageViewModel()
{
Items = new ObservableCollection<string>(Enumerable.Range(1, 10000).Select(i => GenerateItem()));
Items = new ObservableCollection<ItemModel>(Enumerable.Range(1, 10000).Select(i => GenerateItem()));
Selection = new SelectionModel<string>();
Selection = new SelectionModel<ItemModel>();
Selection.Select(1);
_selectionMode = this.WhenAnyValue(
@ -58,8 +59,8 @@ namespace ControlCatalog.ViewModels
});
}
public ObservableCollection<string> Items { get; }
public SelectionModel<string> Selection { get; }
public ObservableCollection<ItemModel> Items { get; }
public SelectionModel<ItemModel> Selection { get; }
public IObservable<SelectionMode> SelectionMode => _selectionMode;
public bool Multiple
@ -96,6 +97,31 @@ namespace ControlCatalog.ViewModels
public MiniCommand RemoveItemCommand { get; }
public MiniCommand SelectRandomItemCommand { get; }
private string GenerateItem() => $"Item {_counter++.ToString()}";
private ItemModel GenerateItem() => new ItemModel(_counter ++);
}
/// <summary>
/// An Item model for the <see cref="ListBoxPage"/>
/// </summary>
public class ItemModel
{
/// <summary>
/// Creates a new ItemModel with the given ID
/// </summary>
/// <param name="id">The ID to display</param>
public ItemModel(int id)
{
ID = id;
}
/// <summary>
/// The ID of this Item
/// </summary>
public int ID { get; }
public override string ToString()
{
return $"Item {ID}";
}
}
}

6
src/Avalonia.Controls/Generators/IItemContainerGenerator.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Styling;
namespace Avalonia.Controls.Generators
@ -24,6 +25,11 @@ namespace Avalonia.Controls.Generators
/// Gets or sets the data template used to display the items in the control.
/// </summary>
IDataTemplate? ItemTemplate { get; set; }
/// <summary>
/// Gets or sets the binding to use to bind to the member of an item used for displaying
/// </summary>
IBinding? DisplayMemberBinding { get; set; }
/// <summary>
/// Gets the ContainerType, or null if its an untyped ContainerGenerator.

13
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -45,6 +45,9 @@ namespace Avalonia.Controls.Generators
/// Gets or sets the data template used to display the items in the control.
/// </summary>
public IDataTemplate? ItemTemplate { get; set; }
/// <inheritdoc />
public IBinding? DisplayMemberBinding { get; set; }
/// <summary>
/// Gets the owner control.
@ -189,7 +192,15 @@ namespace Avalonia.Controls.Generators
if (result == null)
{
result = new ContentPresenter();
result.SetValue(ContentPresenter.ContentProperty, item, BindingPriority.Style);
if (DisplayMemberBinding is not null)
{
result.SetValue(StyledElement.DataContextProperty, item, BindingPriority.Style);
result.Bind(ContentPresenter.ContentProperty, DisplayMemberBinding, BindingPriority.Style);
}
else
{
result.SetValue(ContentPresenter.ContentProperty, item, BindingPriority.Style);
}
if (ItemTemplate != null)
{

12
src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs

@ -53,8 +53,16 @@ namespace Avalonia.Controls.Generators
container.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style);
}
container.SetValue(ContentProperty, item, BindingPriority.Style);
if (DisplayMemberBinding is not null)
{
container.SetValue(StyledElement.DataContextProperty, item, BindingPriority.Style);
container.Bind(ContentProperty, DisplayMemberBinding, BindingPriority.Style);
}
else
{
container.SetValue(ContentProperty, item, BindingPriority.Style);
}
if (!(item is IControl))
{
container.DataContext = item;

7
src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.LogicalTree;
using Avalonia.Reactive;
using Avalonia.VisualTree;
@ -33,6 +34,12 @@ namespace Avalonia.Controls.Generators
TabControl.ItemTemplateProperty));
}
if (Owner.HeaderDisplayMemberBinding is not null)
{
tabItem.Bind(HeaderedContentControl.HeaderProperty, Owner.HeaderDisplayMemberBinding,
BindingPriority.Style);
}
if (tabItem.Header == null)
{
if (item is IHeadered headered)

10
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -76,7 +76,15 @@ namespace Avalonia.Controls.Generators
result.SetValue(Control.ThemeProperty, ItemContainerTheme, BindingPriority.Style);
}
result.SetValue(ContentProperty, template.Build(item), BindingPriority.Style);
if (DisplayMemberBinding is not null)
{
result.SetValue(StyledElement.DataContextProperty, item, BindingPriority.Style);
result.Bind(ContentProperty, DisplayMemberBinding, BindingPriority.Style);
}
else
{
result.SetValue(ContentProperty, template.Build(item), BindingPriority.Style);
}
var itemsSelector = template.ItemsSelector(item);

19
src/Avalonia.Controls/ItemsControl.cs

@ -11,6 +11,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
@ -61,6 +62,23 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
AvaloniaProperty.Register<ItemsControl, IDataTemplate?>(nameof(ItemTemplate));
/// <summary>
/// Defines the <see cref="DisplayMemberBinding" /> property
/// </summary>
public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty =
AvaloniaProperty.Register<ItemsControl, IBinding?>(nameof(DisplayMemberBinding));
/// <summary>
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
/// </summary>
[AssignBinding]
public IBinding? DisplayMemberBinding
{
get { return GetValue(DisplayMemberBindingProperty); }
set { SetValue(DisplayMemberBindingProperty, value); }
}
private IEnumerable? _items = new AvaloniaList<object>();
private int _itemCount;
private IItemContainerGenerator? _itemContainerGenerator;
@ -97,6 +115,7 @@ namespace Avalonia.Controls
_itemContainerGenerator.ItemContainerTheme = ItemContainerTheme;
_itemContainerGenerator.ItemTemplate = ItemTemplate;
_itemContainerGenerator.DisplayMemberBinding = DisplayMemberBinding;
_itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e);
_itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e);
_itemContainerGenerator.Recycled += (_, e) => OnContainersRecycled(e);

17
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@ -5,6 +5,7 @@ using Avalonia.Collections;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.LogicalTree;
using Avalonia.Styling;
@ -33,6 +34,12 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
ItemsControl.ItemTemplateProperty.AddOwner<ItemsPresenterBase>();
/// <summary>
/// Defines the <see cref="DisplayMemberBinding" /> property
/// </summary>
public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty =
ItemsControl.DisplayMemberBindingProperty.AddOwner<ItemsPresenterBase>();
private IEnumerable? _items;
private IDisposable? _itemsSubscription;
private bool _createdPanel;
@ -120,6 +127,15 @@ namespace Avalonia.Controls.Presenters
set { SetValue(ItemTemplateProperty, value); }
}
/// <summary>
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
/// </summary>
public IBinding? DisplayMemberBinding
{
get { return GetValue(DisplayMemberBindingProperty); }
set { SetValue(DisplayMemberBindingProperty, value); }
}
/// <summary>
/// Gets the panel used to display the items.
/// </summary>
@ -177,6 +193,7 @@ namespace Avalonia.Controls.Presenters
{
result = new ItemContainerGenerator(this);
result.ItemTemplate = ItemTemplate;
result.DisplayMemberBinding = DisplayMemberBinding;
}
result.Materialized += ContainerActionHandler;

17
src/Avalonia.Controls/TabControl.cs

@ -12,6 +12,7 @@ using Avalonia.LogicalTree;
using Avalonia.VisualTree;
using Avalonia.Automation;
using Avalonia.Controls.Metadata;
using Avalonia.Data;
namespace Avalonia.Controls
{
@ -57,6 +58,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IDataTemplate?> SelectedContentTemplateProperty =
AvaloniaProperty.Register<TabControl, IDataTemplate?>(nameof(SelectedContentTemplate));
/// <summary>
/// Defines the <see cref="HeaderDisplayMemberBinding" /> property
/// </summary>
public static readonly StyledProperty<IBinding?> HeaderDisplayMemberBindingProperty =
AvaloniaProperty.Register<HeaderedItemsControl, IBinding?>(nameof(HeaderDisplayMemberBinding));
/// <summary>
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
/// </summary>
@ -134,6 +141,16 @@ namespace Avalonia.Controls
get { return GetValue(SelectedContentTemplateProperty); }
internal set { SetValue(SelectedContentTemplateProperty, value); }
}
/// <summary>
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each tab-items header.
/// </summary>
[AssignBinding]
public IBinding? HeaderDisplayMemberBinding
{
get { return GetValue(HeaderDisplayMemberBindingProperty); }
set { SetValue(HeaderDisplayMemberBindingProperty, value); }
}
internal ItemsPresenter? ItemsPresenterPart { get; private set; }

20
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -4,6 +4,7 @@ using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Styling;
@ -736,6 +737,25 @@ namespace Avalonia.Controls.UnitTests
root.Child = null;
root.Child = target;
}
[Fact]
public void Should_Use_DisplayMemberBinding()
{
var target = new ItemsControl
{
Template = GetTemplate(),
DisplayMemberBinding = new Binding("Length")
};
target.Items = new[] { "Foo" };
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var container = (ContentPresenter)target.Presenter.Panel.Children[0];
container.UpdateChild();
Assert.Equal(container.Child!.GetValue(TextBlock.TextProperty), "3");
}
private class Item
{

Loading…
Cancel
Save