Merge pull request #456 from irihitech/issue-455

Fix breadcrumb indexing when insertion from itemssource.
This commit is contained in:
Dong Bin
2024-11-01 14:32:08 +08:00
committed by GitHub
3 changed files with 84 additions and 17 deletions

View File

@@ -5,6 +5,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Ursa.Controls; namespace Ursa.Controls;
@@ -84,17 +85,6 @@ public class Breadcrumb: ItemsControl
return new BreadcrumbItem(); return new BreadcrumbItem();
} }
protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
base.LogicalChildrenCollectionChanged(sender, e);
var breadcrumbItems = LogicalChildren.OfType<BreadcrumbItem>().ToList();
for (var i = 0; i < breadcrumbItems.Count; i++)
{
var breadcrumbItem = breadcrumbItems[i];
breadcrumbItem.SetPseudoClassLast(i == breadcrumbItems.Count - 1);
}
}
protected override void PrepareContainerForItemOverride(Control container, object? item, int index) protected override void PrepareContainerForItemOverride(Control container, object? item, int index)
{ {
if (container is not BreadcrumbItem breadcrumbItem) return; if (container is not BreadcrumbItem breadcrumbItem) return;
@@ -149,4 +139,13 @@ public class Breadcrumb: ItemsControl
ITemplate<Control?> t => t.Build(), ITemplate<Control?> t => t.Build(),
_ => separator.ToString() _ => separator.ToString()
}; };
internal void InvalidateContainers()
{
var breadcrumbItems = this.GetVisualDescendants().OfType<BreadcrumbItem>().ToList();
for (var i = 0; i < breadcrumbItems.Count; i++)
{
breadcrumbItems[i].SetPseudoClassLast(i == breadcrumbItems.Count - 1);
}
}
} }

View File

@@ -79,4 +79,11 @@ public class BreadcrumbItem: ContentControl
{ {
PseudoClasses.Set(PC_Last, isLast); PseudoClasses.Set(PC_Last, isLast);
} }
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
var parent = this.Parent as Breadcrumb;
parent?.InvalidateContainers();
}
} }

View File

@@ -2,6 +2,7 @@ using System.Collections.ObjectModel;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Headless.XUnit; using Avalonia.Headless.XUnit;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.VisualTree;
using Ursa.Controls; using Ursa.Controls;
namespace HeadlessTest.Ursa.Controls; namespace HeadlessTest.Ursa.Controls;
@@ -24,6 +25,22 @@ public class BreadcrumbTests
Assert.DoesNotContain(":last", item1.Classes); Assert.DoesNotContain(":last", item1.Classes);
} }
[AvaloniaFact]
public void BreadcrumbItem_Should_Have_Correct_PseudoClasses_When_Insert()
{
var window = new Window();
var breadcrumb = new Breadcrumb();
window.Content = breadcrumb;
window.Show();
var item1 = new BreadcrumbItem();
var item2 = new BreadcrumbItem();
breadcrumb.Items.Add(item1);
Assert.Contains(":last", item1.Classes);
breadcrumb.Items.Insert(0, item2);
Assert.Contains(":last", item1.Classes);
Assert.DoesNotContain(":last", item2.Classes);
}
[AvaloniaFact] [AvaloniaFact]
public void Generated_BreadcrumbItem_Should_Have_Correct_PseudoClasses() public void Generated_BreadcrumbItem_Should_Have_Correct_PseudoClasses()
{ {
@@ -34,16 +51,38 @@ public class BreadcrumbTests
var item1 = new TextBlock(); var item1 = new TextBlock();
var item2 = new TextBlock(); var item2 = new TextBlock();
breadcrumb.Items.Add(item1); breadcrumb.Items.Add(item1);
var firstItem = breadcrumb.GetLogicalChildren().OfType<BreadcrumbItem>().FirstOrDefault(); var firstItem = breadcrumb.GetVisualDescendants().OfType<BreadcrumbItem>().FirstOrDefault();
Assert.NotNull(firstItem); Assert.NotNull(firstItem);
Assert.Contains(":last", firstItem.Classes); Assert.Contains(":last", firstItem.Classes);
breadcrumb.Items.Add(item2); breadcrumb.Items.Add(item2);
var lastItem = breadcrumb.GetLogicalChildren().OfType<BreadcrumbItem>().LastOrDefault(); var lastItem = breadcrumb.GetVisualDescendants().OfType<BreadcrumbItem>().LastOrDefault();
Assert.NotNull(lastItem); Assert.NotNull(lastItem);
Assert.Contains(":last", lastItem.Classes); Assert.Contains(":last", lastItem.Classes);
Assert.DoesNotContain(":last", firstItem.Classes); Assert.DoesNotContain(":last", firstItem.Classes);
} }
[AvaloniaFact]
public void Generated_BreadcrumbItem_Should_Have_Correct_PseudoClasses_When_Insert()
{
var window = new Window();
var breadcrumb = new Breadcrumb();
window.Content = breadcrumb;
window.Show();
var item1 = new TextBlock();
var item2 = new TextBlock();
breadcrumb.Items.Add(item1);
var firstItem = breadcrumb.GetVisualDescendants().OfType<BreadcrumbItem>().FirstOrDefault();
Assert.NotNull(firstItem);
Assert.Contains(":last", firstItem.Classes);
breadcrumb.Items.Insert(0, item2);
var lastItem = breadcrumb.GetVisualDescendants().OfType<BreadcrumbItem>().LastOrDefault();
Assert.NotNull(lastItem);
Assert.Contains(":last", lastItem.Classes);
firstItem = breadcrumb.GetVisualDescendants().OfType<BreadcrumbItem>().FirstOrDefault();
Assert.NotNull(firstItem);
Assert.DoesNotContain(":last", firstItem.Classes);
}
[AvaloniaFact] [AvaloniaFact]
public void BreadcrumbItem_FromItemsSource_Should_Have_Correct_PseudoClasses() public void BreadcrumbItem_FromItemsSource_Should_Have_Correct_PseudoClasses()
{ {
@@ -54,16 +93,38 @@ public class BreadcrumbTests
var items = new ObservableCollection<string>(); var items = new ObservableCollection<string>();
breadcrumb.ItemsSource = items; breadcrumb.ItemsSource = items;
items.Add("Item 1"); items.Add("Item 1");
var item1 = breadcrumb.GetLogicalChildren().OfType<BreadcrumbItem>().FirstOrDefault(); var item1 = breadcrumb.GetVisualDescendants().OfType<BreadcrumbItem>().FirstOrDefault();
Assert.NotNull(item1); Assert.NotNull(item1);
Assert.Contains(":last", item1.Classes); Assert.Contains(":last", item1.Classes);
items.Add("Item 2"); items.Add("Item 2");
var item2 = breadcrumb.GetLogicalChildren().OfType<BreadcrumbItem>().LastOrDefault(); var item2 = breadcrumb.GetVisualDescendants().OfType<BreadcrumbItem>().LastOrDefault();
Assert.NotNull(item2); Assert.NotNull(item2);
Assert.Contains(":last", item2.Classes); Assert.Contains(":last", item2.Classes);
Assert.DoesNotContain(":last", item1.Classes); Assert.DoesNotContain(":last", item1.Classes);
} }
[AvaloniaFact]
public void BreadcrumbItem_FromItemsSource_Should_Have_Correct_PseudoClasses_When_Insert()
{
var window = new Window();
var breadcrumb = new Breadcrumb();
window.Content = breadcrumb;
window.Show();
var items = new ObservableCollection<string>();
breadcrumb.ItemsSource = items;
items.Add("Item 1");
var item1 = breadcrumb.GetVisualDescendants().OfType<BreadcrumbItem>().FirstOrDefault();
Assert.NotNull(item1);
Assert.Contains(":last", item1.Classes);
items.Insert(0, "Item 2");
var item2 = breadcrumb.GetVisualDescendants().OfType<BreadcrumbItem>().LastOrDefault();
Assert.NotNull(item2);
Assert.Contains(":last", item2.Classes);
var newItem1 = breadcrumb.GetVisualDescendants().OfType<BreadcrumbItem>().FirstOrDefault();
Assert.NotNull(newItem1);
Assert.DoesNotContain(":last", newItem1.Classes);
}
[AvaloniaFact] [AvaloniaFact]
public void Bulk_Added_BreadcrumbItem_Should_Have_Correct_PseudoClasses() public void Bulk_Added_BreadcrumbItem_Should_Have_Correct_PseudoClasses()
{ {
@@ -77,8 +138,8 @@ public class BreadcrumbTests
"Item 2" "Item 2"
}; };
breadcrumb.ItemsSource = items; breadcrumb.ItemsSource = items;
var item1 = breadcrumb.GetLogicalChildren().OfType<BreadcrumbItem>().FirstOrDefault(); var item1 = breadcrumb.GetVisualDescendants().OfType<BreadcrumbItem>().FirstOrDefault();
var item2 = breadcrumb.GetLogicalChildren().OfType<BreadcrumbItem>().LastOrDefault(); var item2 = breadcrumb.GetVisualDescendants().OfType<BreadcrumbItem>().LastOrDefault();
Assert.NotNull(item1); Assert.NotNull(item1);
Assert.NotNull(item2); Assert.NotNull(item2);
Assert.Contains(":last", item2.Classes); Assert.Contains(":last", item2.Classes);