Browse Source

Fix TabItem's Content inheriting TabControl's DataContext instead of TabItem's (#20541)

* Child inherit DataContext from TabItem

* Remove whitespace changes

* Fix nullability error

* Fix using Julien's solution
pull/20669/head
Petar Tasev 1 month ago
committed by GitHub
parent
commit
b57223eb1b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 25
      src/Avalonia.Controls/TabControl.cs
  2. 77
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs

25
src/Avalonia.Controls/TabControl.cs

@ -1,4 +1,4 @@
using System.Linq;
using System.Diagnostics;
using Avalonia.Collections;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Presenters;
@ -7,7 +7,6 @@ using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
using Avalonia.Automation;
using Avalonia.Controls.Metadata;
using Avalonia.Reactive;
@ -76,7 +75,7 @@ namespace Avalonia.Controls
SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
ItemsPanelProperty.OverrideDefaultValue<TabControl>(DefaultPanel);
AffectsMeasure<TabControl>(TabStripPlacementProperty);
SelectedItemProperty.Changed.AddClassHandler<TabControl>((x, e) => x.UpdateSelectedContent());
SelectedItemProperty.Changed.AddClassHandler<TabControl>((x, _) => x.UpdateSelectedContent());
AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue<TabControl>(AutomationControlType.Tab);
}
@ -231,7 +230,25 @@ namespace Avalonia.Controls
}
_selectedItemSubscriptions = new CompositeDisposable(
container.GetObservable(ContentControl.ContentProperty).Subscribe(v => SelectedContent = v),
container.GetObservable(ContentControl.ContentProperty).Subscribe(content =>
{
var contentElement = content as StyledElement;
var contentDataContext = contentElement?.DataContext;
SelectedContent = content;
// When the ContentPresenter (ContentPart) displays content that is a Control, it doesn't
// set its DataContext to that of the Control's. If the content doesn't set a DataContext,
// then it gets inherited from the TabControl. Work around this issue by setting the
// DataContext of the ContentPart to the content's original DataContext (inherited from
// container).
if (contentElement is not null &&
contentElement.DataContext != contentDataContext &&
ContentPart is not null)
{
Debug.Assert(!contentElement.IsSet(DataContextProperty));
ContentPart.DataContext = contentDataContext;
}
}),
container.GetObservable(ContentControl.ContentTemplateProperty).Subscribe(v => SelectedContentTemplate = SelectContentTemplate(v)));
// Note how we fall back to our own ContentTemplate if the container doesn't specify one

77
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@ -9,6 +9,8 @@ using Avalonia.Controls.Selection;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Harfbuzz;
using Avalonia.Headless;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.LogicalTree;
@ -259,51 +261,48 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void DataContexts_Should_Be_Correctly_Set()
{
using var app = Start();
var items = new object[]
{
"Foo",
new Item("Bar"),
new TextBlock { Text = "Baz" },
new TabItem { Content = "Qux" },
new TabItem { Content = new TextBlock { Text = "Bob" } }
new TabItem { Content = new TextBlock { Text = "Bob" } },
new TabItem { DataContext = "Rob", Content = new TextBlock { Text = "Bob" } },
};
var target = new TabControl
{
Template = TabControlTemplate(),
DataContext = "Base",
DataTemplates =
{
new FuncDataTemplate<Item>((x, __) => new Button { Content = x })
},
ItemsSource = items,
};
ApplyTemplate(target);
var root = CreateRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
target.ContentPart!.UpdateChild();
var dataContext = ((TextBlock)target.ContentPart.Child!).DataContext;
var dataContext = ((TextBlock)target.ContentPart!.Child!).DataContext;
Assert.Equal(items[0], dataContext);
target.SelectedIndex = 1;
target.ContentPart.UpdateChild();
dataContext = ((Button)target.ContentPart.Child).DataContext;
Assert.Equal(items[1], dataContext);
target.SelectedIndex = 2;
target.ContentPart.UpdateChild();
dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
Assert.Equal("Base", dataContext);
target.SelectedIndex = 3;
target.ContentPart.UpdateChild();
dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
Assert.Equal("Qux", dataContext);
target.SelectedIndex = 4;
target.ContentPart.UpdateChild();
dataContext = target.ContentPart.DataContext;
Assert.Equal("Base", dataContext);
target.SelectedIndex = 5;
dataContext = target.ContentPart.Child.DataContext;
Assert.Equal("Rob", dataContext);
}
/// <summary>
@ -843,6 +842,45 @@ namespace Avalonia.Controls.UnitTests
}.RegisterInNameScope(scope));
}
private static ControlTheme CreateTabControlControlTheme()
{
return new ControlTheme(typeof(TabControl))
{
Setters =
{
new Setter(TabControl.TemplateProperty, TabControlTemplate()),
},
};
}
private static ControlTheme CreateTabItemControlTheme()
{
return new ControlTheme(typeof(TabItem))
{
Setters =
{
new Setter(TabItem.TemplateProperty, TabItemTemplate()),
},
};
}
private static TestRoot CreateRoot(Control child)
{
return new TestRoot
{
Resources =
{
{ typeof(TabControl), CreateTabControlControlTheme() },
{ typeof(TabItem), CreateTabItemControlTheme() },
},
DataTemplates =
{
new FuncDataTemplate<Item>((x, _) => new Button { Content = x.Value })
},
Child = child,
};
}
private class TestTopLevel : TopLevel
{
private readonly ILayoutManager _layoutManager;
@ -892,6 +930,19 @@ namespace Avalonia.Controls.UnitTests
target.ContentPart!.ApplyTemplate();
}
private IDisposable Start()
{
return UnitTestApplication.Start(
TestServices.MockThreadingInterface.With(
fontManagerImpl: new HeadlessFontManagerStub(),
keyboardDevice: () => new KeyboardDevice(),
keyboardNavigation: () => new KeyboardNavigationHandler(),
inputManager: new InputManager(),
renderInterface: new HeadlessPlatformRenderInterface(),
textShaperImpl: new HarfBuzzTextShaper(),
assetLoader: new StandardAssetLoader()));
}
private class Item
{
public Item(string value)

Loading…
Cancel
Save