Browse Source

Reimplement DisplayMemberBinding.

Use a slightly different approach to the one that was previously there: create an item template that contains `DisplayMemberBinding`. This is the approach that WPF broadly uses too.
Steven Kirk 3 years ago
parent
commit
004103a925

+ 32 - 3
src/Avalonia.Controls/ItemsControl.cs

@@ -83,6 +83,7 @@ namespace Avalonia.Controls
         private int _itemCount;
         private ItemContainerGenerator? _itemContainerGenerator;
         private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
+        private IDataTemplate? _displayMemberItemTemplate;
 
         /// <summary>
         /// Initializes static members of the <see cref="ItemsControl"/> class.
@@ -291,19 +292,19 @@ namespace Avalonia.Controls
                 else if (item is not Visual)
                     hcc.Header = item;
 
-                if (ItemTemplate is { } it)
+                if (GetEffectiveItemTemplate() is { } it)
                     hcc.HeaderTemplate = it;
             }
             else if (container is ContentControl cc)
             {
                 cc.Content = item;
-                if (ItemTemplate is { } it)
+                if (GetEffectiveItemTemplate() is { } it)
                     cc.ContentTemplate = it;
             }
             else if (container is ContentPresenter p)
             {
                 p.Content = item;
-                if (ItemTemplate is { } it)
+                if (GetEffectiveItemTemplate() is { } it)
                     p.ContentTemplate = it;
             }
         }
@@ -431,6 +432,17 @@ namespace Avalonia.Controls
                 throw new NotImplementedException();
                 ////_itemContainerGenerator.ItemContainerTheme = change.GetNewValue<ControlTheme?>();
             }
+            else if (change.Property == ItemTemplateProperty)
+            {
+                if (change.NewValue is not null && DisplayMemberBinding is not null)
+                    throw new InvalidOperationException("Cannot set both DisplayMemberBinding and ItemTemplate.");
+            }
+            else if (change.Property == DisplayMemberBindingProperty)
+            {
+                if (change.NewValue is not null && ItemTemplate is not null)
+                    throw new InvalidOperationException("Cannot set both DisplayMemberBinding and ItemTemplate.");
+                _displayMemberItemTemplate = null;
+            }
         }
 
         /// <summary>
@@ -568,6 +580,23 @@ namespace Avalonia.Controls
             }
         }
 
+        private IDataTemplate? GetEffectiveItemTemplate()
+        {
+            if (ItemTemplate is { } itemTemplate)
+                return itemTemplate;
+
+            if (_displayMemberItemTemplate is null && DisplayMemberBinding is { } binding)
+            {
+                _displayMemberItemTemplate = new FuncDataTemplate<object?>((_, _) =>
+                    new TextBlock
+                    {
+                        [!TextBlock.TextProperty] = binding,
+                    });
+            }
+
+            return _displayMemberItemTemplate;
+        }
+
         /// <summary>
         /// Called when the <see cref="ItemTemplate"/> changes.
         /// </summary>

+ 25 - 0
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@@ -708,6 +708,31 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(container.Child!.GetValue(TextBlock.TextProperty), "3");
         }
 
+        [Fact]
+        public void Cannot_Set_Both_DisplayMemberBinding_And_ItemTemplate_1()
+        {
+            var target = new ItemsControl
+            {
+                Template = GetTemplate(),
+                DisplayMemberBinding = new Binding("Length")
+            };
+
+            Assert.Throws<InvalidOperationException>(() => 
+                target.ItemTemplate = new FuncDataTemplate<string>((_, _) => new TextBlock()));
+        }
+
+        [Fact]
+        public void Cannot_Set_Both_DisplayMemberBinding_And_ItemTemplate_2()
+        {
+            var target = new ItemsControl
+            {
+                Template = GetTemplate(),
+                ItemTemplate = new FuncDataTemplate<string>((_, _) => new TextBlock()),
+            };
+
+            Assert.Throws<InvalidOperationException>(() => target.DisplayMemberBinding = new Binding("Length"));
+        }
+
         private class Item
         {
             public Item(string value)