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
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
using System.Linq;
|
using System.Diagnostics;
|
||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using Avalonia.Automation.Peers;
|
using Avalonia.Automation.Peers;
|
||||||
using Avalonia.Controls.Presenters;
|
using Avalonia.Controls.Presenters;
|
||||||
@@ -7,7 +7,6 @@ using Avalonia.Controls.Templates;
|
|||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Layout;
|
using Avalonia.Layout;
|
||||||
using Avalonia.LogicalTree;
|
using Avalonia.LogicalTree;
|
||||||
using Avalonia.VisualTree;
|
|
||||||
using Avalonia.Automation;
|
using Avalonia.Automation;
|
||||||
using Avalonia.Controls.Metadata;
|
using Avalonia.Controls.Metadata;
|
||||||
using Avalonia.Reactive;
|
using Avalonia.Reactive;
|
||||||
@@ -76,7 +75,7 @@ namespace Avalonia.Controls
|
|||||||
SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
|
SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
|
||||||
ItemsPanelProperty.OverrideDefaultValue<TabControl>(DefaultPanel);
|
ItemsPanelProperty.OverrideDefaultValue<TabControl>(DefaultPanel);
|
||||||
AffectsMeasure<TabControl>(TabStripPlacementProperty);
|
AffectsMeasure<TabControl>(TabStripPlacementProperty);
|
||||||
SelectedItemProperty.Changed.AddClassHandler<TabControl>((x, e) => x.UpdateSelectedContent());
|
SelectedItemProperty.Changed.AddClassHandler<TabControl>((x, _) => x.UpdateSelectedContent());
|
||||||
AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue<TabControl>(AutomationControlType.Tab);
|
AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue<TabControl>(AutomationControlType.Tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +230,25 @@ namespace Avalonia.Controls
|
|||||||
}
|
}
|
||||||
|
|
||||||
_selectedItemSubscriptions = new CompositeDisposable(
|
_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)));
|
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
|
// Note how we fall back to our own ContentTemplate if the container doesn't specify one
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ using Avalonia.Controls.Selection;
|
|||||||
using Avalonia.Controls.Templates;
|
using Avalonia.Controls.Templates;
|
||||||
using Avalonia.Controls.Utils;
|
using Avalonia.Controls.Utils;
|
||||||
using Avalonia.Data;
|
using Avalonia.Data;
|
||||||
|
using Avalonia.Harfbuzz;
|
||||||
|
using Avalonia.Headless;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Layout;
|
using Avalonia.Layout;
|
||||||
using Avalonia.LogicalTree;
|
using Avalonia.LogicalTree;
|
||||||
@@ -259,51 +261,48 @@ namespace Avalonia.Controls.UnitTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void DataContexts_Should_Be_Correctly_Set()
|
public void DataContexts_Should_Be_Correctly_Set()
|
||||||
{
|
{
|
||||||
|
using var app = Start();
|
||||||
var items = new object[]
|
var items = new object[]
|
||||||
{
|
{
|
||||||
"Foo",
|
"Foo",
|
||||||
new Item("Bar"),
|
new Item("Bar"),
|
||||||
new TextBlock { Text = "Baz" },
|
new TextBlock { Text = "Baz" },
|
||||||
new TabItem { Content = "Qux" },
|
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
|
var target = new TabControl
|
||||||
{
|
{
|
||||||
Template = TabControlTemplate(),
|
|
||||||
DataContext = "Base",
|
DataContext = "Base",
|
||||||
DataTemplates =
|
|
||||||
{
|
|
||||||
new FuncDataTemplate<Item>((x, __) => new Button { Content = x })
|
|
||||||
},
|
|
||||||
ItemsSource = items,
|
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);
|
Assert.Equal(items[0], dataContext);
|
||||||
|
|
||||||
target.SelectedIndex = 1;
|
target.SelectedIndex = 1;
|
||||||
target.ContentPart.UpdateChild();
|
|
||||||
dataContext = ((Button)target.ContentPart.Child).DataContext;
|
dataContext = ((Button)target.ContentPart.Child).DataContext;
|
||||||
Assert.Equal(items[1], dataContext);
|
Assert.Equal(items[1], dataContext);
|
||||||
|
|
||||||
target.SelectedIndex = 2;
|
target.SelectedIndex = 2;
|
||||||
target.ContentPart.UpdateChild();
|
|
||||||
dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
|
dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
|
||||||
Assert.Equal("Base", dataContext);
|
Assert.Equal("Base", dataContext);
|
||||||
|
|
||||||
target.SelectedIndex = 3;
|
target.SelectedIndex = 3;
|
||||||
target.ContentPart.UpdateChild();
|
|
||||||
dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
|
dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
|
||||||
Assert.Equal("Qux", dataContext);
|
Assert.Equal("Qux", dataContext);
|
||||||
|
|
||||||
target.SelectedIndex = 4;
|
target.SelectedIndex = 4;
|
||||||
target.ContentPart.UpdateChild();
|
|
||||||
dataContext = target.ContentPart.DataContext;
|
dataContext = target.ContentPart.DataContext;
|
||||||
Assert.Equal("Base", dataContext);
|
Assert.Equal("Base", dataContext);
|
||||||
|
|
||||||
|
target.SelectedIndex = 5;
|
||||||
|
dataContext = target.ContentPart.Child.DataContext;
|
||||||
|
Assert.Equal("Rob", dataContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -843,6 +842,45 @@ namespace Avalonia.Controls.UnitTests
|
|||||||
}.RegisterInNameScope(scope));
|
}.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 class TestTopLevel : TopLevel
|
||||||
{
|
{
|
||||||
private readonly ILayoutManager _layoutManager;
|
private readonly ILayoutManager _layoutManager;
|
||||||
@@ -892,6 +930,19 @@ namespace Avalonia.Controls.UnitTests
|
|||||||
target.ContentPart!.ApplyTemplate();
|
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
|
private class Item
|
||||||
{
|
{
|
||||||
public Item(string value)
|
public Item(string value)
|
||||||
|
|||||||
Reference in New Issue
Block a user