From 01c6bf4bb79c006d8998d3994b1c04bc9bb08ed7 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 9 Sep 2022 12:48:43 +0200 Subject: [PATCH 1/8] Add DisplayMemberBinding to ItemsControl --- .../Generators/IItemContainerGenerator.cs | 6 ++++++ .../Generators/ItemContainerGenerator.cs | 13 ++++++++++++- .../Generators/ItemContainerGenerator`1.cs | 12 ++++++++++-- .../Generators/TreeItemContainerGenerator.cs | 10 +++++++++- src/Avalonia.Controls/ItemsControl.cs | 19 +++++++++++++++++++ .../Presenters/ItemsPresenterBase.cs | 17 +++++++++++++++++ 6 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs index f9772cb399..79c77f2519 100644 --- a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs +++ b/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. /// IDataTemplate? ItemTemplate { get; set; } + + /// + /// Gets or sets the binding to use to bind to the member of an item used for displaying + /// + IBinding? DisplayMemberBinding { get; set; } /// /// Gets the ContainerType, or null if its an untyped ContainerGenerator. diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs index 8b36b07cec..42f0124295 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs +++ b/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. /// public IDataTemplate? ItemTemplate { get; set; } + + /// + public IBinding? DisplayMemberBinding { get; set; } /// /// 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) { diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs index 3ff1b0702d..5e965a9d04 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs +++ b/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; diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index 4e3deb5552..9f9845b14f 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/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, item, BindingPriority.Style); + } var itemsSelector = template.ItemsSelector(item); diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 345e7fcac8..e9ce7912a7 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/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 ItemTemplateProperty = AvaloniaProperty.Register(nameof(ItemTemplate)); + + /// + /// Defines the property + /// + public static readonly StyledProperty DisplayMemberBindingProperty = + AvaloniaProperty.Register(nameof(DisplayMemberBinding)); + + /// + /// Gets or sets the to use for binding to the display member of each item. + /// + [AssignBinding] + public IBinding? DisplayMemberBinding + { + get { return GetValue(DisplayMemberBindingProperty); } + set { SetValue(DisplayMemberBindingProperty, value); } + } + private IEnumerable? _items = new AvaloniaList(); 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); diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs index 2821fa8cf0..836433cdf8 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs +++ b/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 ItemTemplateProperty = ItemsControl.ItemTemplateProperty.AddOwner(); + /// + /// Defines the property + /// + public static readonly StyledProperty DisplayMemberBindingProperty = + ItemsControl.DisplayMemberBindingProperty.AddOwner(); + private IEnumerable? _items; private IDisposable? _itemsSubscription; private bool _createdPanel; @@ -120,6 +127,15 @@ namespace Avalonia.Controls.Presenters set { SetValue(ItemTemplateProperty, value); } } + /// + /// Gets or sets the to use for binding to the display member of each item. + /// + public IBinding? DisplayMemberBinding + { + get { return GetValue(DisplayMemberBindingProperty); } + set { SetValue(DisplayMemberBindingProperty, value); } + } + /// /// Gets the panel used to display the items. /// @@ -177,6 +193,7 @@ namespace Avalonia.Controls.Presenters { result = new ItemContainerGenerator(this); result.ItemTemplate = ItemTemplate; + result.DisplayMemberBinding = DisplayMemberBinding; } result.Materialized += ContainerActionHandler; From 30e46518693d4bb2e14ddede6b2c00448cd0fc31 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 19 Sep 2022 13:48:26 +0200 Subject: [PATCH 2/8] Add TabItem HeaderDisplayMemberBinding --- .../Generators/TabItemContainerGenerator.cs | 7 +++++++ src/Avalonia.Controls/TabControl.cs | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs index c6b0bda9af..4021b8436a 100644 --- a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs +++ b/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) diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 70fecc7ce1..63738716c0 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/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 SelectedContentTemplateProperty = AvaloniaProperty.Register(nameof(SelectedContentTemplate)); + /// + /// Defines the property + /// + public static readonly StyledProperty HeaderDisplayMemberBindingProperty = + AvaloniaProperty.Register(nameof(HeaderDisplayMemberBinding)); + /// /// The default value for the property. /// @@ -134,6 +141,16 @@ namespace Avalonia.Controls get { return GetValue(SelectedContentTemplateProperty); } internal set { SetValue(SelectedContentTemplateProperty, value); } } + + /// + /// Gets or sets the to use for binding to the display member of each tab-items header. + /// + [AssignBinding] + public IBinding? HeaderDisplayMemberBinding + { + get { return GetValue(HeaderDisplayMemberBindingProperty); } + set { SetValue(HeaderDisplayMemberBindingProperty, value); } + } internal ItemsPresenter? ItemsPresenterPart { get; private set; } From dcebd4f5ab8a496b7f2a91490806f676241e6fd7 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 19 Sep 2022 13:49:22 +0200 Subject: [PATCH 3/8] Update TabControl-Demo to use the new added HeaderDisplayMemberBinding --- samples/ControlCatalog/Pages/TabControlPage.xaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml index cba6fcd0ad..90afb5ceca 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml @@ -51,15 +51,9 @@ - - - - - - - + From ab4bb208f56eee4a06dd052eece7afa642e5753f Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 22 Sep 2022 14:31:02 +0200 Subject: [PATCH 4/8] Add Unit Test --- .../ItemsControlTests.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index f08653a4f8..944f974cb0 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/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 { From 42e27d89508a5a523f24c1eb1f2f272154b6a374 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 7 Oct 2022 15:40:23 +0200 Subject: [PATCH 5/8] Fix Tests --- src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index 9f9845b14f..2d8cb05e03 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs @@ -83,7 +83,7 @@ namespace Avalonia.Controls.Generators } else { - result.SetValue(ContentProperty, item, BindingPriority.Style); + result.SetValue(ContentProperty, template.Build(item), BindingPriority.Style); } var itemsSelector = template.ItemsSelector(item); From 2a2add7d4dbb54c271296f17a545d28d3f829cf6 Mon Sep 17 00:00:00 2001 From: Tim <47110241+timunie@users.noreply.github.com> Date: Mon, 10 Oct 2022 14:59:39 +0200 Subject: [PATCH 6/8] Update ListBoxPage: - Added ItemModel - Enable CompiledBindings - Use DisplayMemberBinding --- samples/ControlCatalog/Pages/ListBoxPage.xaml | 4 +++ .../ViewModels/ListBoxPageViewModel.cs | 36 ++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index 433592345a..067d4d9890 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -1,5 +1,8 @@ @@ -30,6 +33,7 @@ diff --git a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs index 59489ebcc0..f89d9d1e20 100644 --- a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs +++ b/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(Enumerable.Range(1, 10000).Select(i => GenerateItem())); + Items = new ObservableCollection(Enumerable.Range(1, 10000).Select(i => GenerateItem())); - Selection = new SelectionModel(); + Selection = new SelectionModel(); Selection.Select(1); _selectionMode = this.WhenAnyValue( @@ -58,8 +59,8 @@ namespace ControlCatalog.ViewModels }); } - public ObservableCollection Items { get; } - public SelectionModel Selection { get; } + public ObservableCollection Items { get; } + public SelectionModel Selection { get; } public IObservable 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 ++); + } + + /// + /// An Item model for the + /// + public class ItemModel + { + /// + /// Creates a new ItemModel with the given ID + /// + /// The ID to display + public ItemModel(int id) + { + ID = id; + } + + /// + /// The ID of this Item + /// + public int ID { get; } + + public override string ToString() + { + return $"Item {ID}"; + } } } From 15ec0eb202410df406de07995475b55bfaf9bf9b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 23 Nov 2022 16:03:36 -0800 Subject: [PATCH 7/8] Update TabControlPage.xaml --- samples/ControlCatalog/Pages/TabControlPage.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml index 8e353cf651..a088d078d3 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml @@ -53,7 +53,7 @@ From d77a38739edc14a751f5d36689f52b91a4d4a687 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 23 Nov 2022 16:18:35 -0800 Subject: [PATCH 8/8] Update TabControlPage.xaml --- samples/ControlCatalog/Pages/TabControlPage.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml index a088d078d3..e289de90f9 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml @@ -56,7 +56,7 @@ HeaderDisplayMemberBinding="{Binding Header, x:DataType=viewModels:TabControlPageViewModelItem}" TabStripPlacement="{Binding TabPlacement}"> - +