diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index ec3bf799b4..06f808b726 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -2,9 +2,6 @@
xmlns:pages="clr-namespace:ControlCatalog.Pages"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
-
-
-
@@ -29,5 +26,6 @@
+
diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml
new file mode 100644
index 0000000000..5b10e7d790
--- /dev/null
+++ b/samples/ControlCatalog/Pages/TabControlPage.xaml
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This is the first page in the TabControl.
+
+
+
+
+
+
+
+
+
+ This is the second page in the TabControl.
+
+
+
+
+
+
+
+
+ You should not see this.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tab Placement:
+
+ Left
+ Bottom
+ Right
+ Top
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs
new file mode 100644
index 0000000000..808d90a49c
--- /dev/null
+++ b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs
@@ -0,0 +1,80 @@
+using System;
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+
+using ReactiveUI;
+
+namespace ControlCatalog.Pages
+{
+ using System.Collections.Generic;
+
+ public class TabControlPage : UserControl
+ {
+ public TabControlPage()
+ {
+ InitializeComponent();
+
+ DataContext = new PageViewModel
+ {
+ Tabs = new[]
+ {
+ new TabItemViewModel
+ {
+ Header = "Arch",
+ Text = "This is the first templated tab page.",
+ Image = LoadBitmap("resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg?assembly=ControlCatalog"),
+ },
+ new TabItemViewModel
+ {
+ Header = "Leaf",
+ Text = "This is the second templated tab page.",
+ Image = LoadBitmap("resm:ControlCatalog.Assets.maple-leaf-888807_640.jpg?assembly=ControlCatalog"),
+ },
+ new TabItemViewModel
+ {
+ Header = "Disabled",
+ Text = "You should not see this.",
+ IsEnabled = false,
+ },
+ },
+ TabPlacement = Dock.Top,
+ };
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private IBitmap LoadBitmap(string uri)
+ {
+ var assets = AvaloniaLocator.Current.GetService();
+ return new Bitmap(assets.Open(new Uri(uri)));
+ }
+
+ private class PageViewModel : ReactiveObject
+ {
+ private Dock _tabPlacement;
+
+ public TabItemViewModel[] Tabs { get; set; }
+
+ public Dock TabPlacement
+ {
+ get { return _tabPlacement; }
+ set { this.RaiseAndSetIfChanged(ref _tabPlacement, value); }
+ }
+ }
+
+ private class TabItemViewModel
+ {
+ public string Header { get; set; }
+ public string Text { get; set; }
+ public IBitmap Image { get; set; }
+ public bool IsEnabled { get; set; } = true;
+ }
+ }
+}
diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml
index cc3c31d13a..3ec8e43b07 100644
--- a/samples/ControlCatalog/SideBar.xaml
+++ b/samples/ControlCatalog/SideBar.xaml
@@ -1,52 +1,67 @@
-
+
-
-
-
-
-
+
+
+
+
+
diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs
index 9ee4787e47..f2f3ed9bfc 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs
@@ -88,18 +88,21 @@ namespace Avalonia.Data.Core
_subscriber(value);
}
- protected void ValueChanged(object value)
+ protected void ValueChanged(object value) => ValueChanged(value, true);
+
+ private void ValueChanged(object value, bool notify)
{
var notification = value as BindingNotification;
if (notification == null)
{
LastValue = new WeakReference(value);
+
if (Next != null)
{
- Next.Target = new WeakReference(value);
+ Next.Target = LastValue;
}
- else
+ else if (notify)
{
_subscriber(value);
}
@@ -110,7 +113,7 @@ namespace Avalonia.Data.Core
if (Next != null)
{
- Next.Target = new WeakReference(notification.Value);
+ Next.Target = LastValue;
}
if (Next == null || notification.Error != null)
@@ -136,6 +139,7 @@ namespace Avalonia.Data.Core
}
else
{
+ ValueChanged(AvaloniaProperty.UnsetValue, notify:false);
_listening = false;
}
}
diff --git a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs
new file mode 100644
index 0000000000..088f9e30ea
--- /dev/null
+++ b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs
@@ -0,0 +1,57 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Controls.Primitives;
+
+namespace Avalonia.Controls.Generators
+{
+ public class TabItemContainerGenerator : ItemContainerGenerator
+ {
+ public TabItemContainerGenerator(TabControl owner)
+ : base(owner, ContentControl.ContentProperty, ContentControl.ContentTemplateProperty)
+ {
+ Owner = owner;
+ }
+
+ public new TabControl Owner { get; }
+
+ protected override IControl CreateContainer(object item)
+ {
+ var tabItem = (TabItem)base.CreateContainer(item);
+
+ tabItem.ParentTabControl = Owner;
+
+ if (tabItem.HeaderTemplate == null)
+ {
+ tabItem[~HeaderedContentControl.HeaderTemplateProperty] = Owner[~ItemsControl.ItemTemplateProperty];
+ }
+
+ if (tabItem.Header == null)
+ {
+ if (item is IHeadered headered)
+ {
+ tabItem.Header = headered.Header;
+ }
+ else
+ {
+ if (!(tabItem.DataContext is IControl))
+ {
+ tabItem.Header = tabItem.DataContext;
+ }
+ }
+ }
+
+ if (!(tabItem.Content is IControl))
+ {
+ tabItem[~ContentControl.ContentTemplateProperty] = Owner[~TabControl.ContentTemplateProperty];
+ }
+
+ if (tabItem.Content == null)
+ {
+ tabItem[~ContentControl.ContentProperty] = tabItem[~StyledElement.DataContextProperty];
+ }
+
+ return tabItem;
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
index d67ebfd489..7a46e0f776 100644
--- a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
+++ b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
@@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using Avalonia.Controls.Templates;
+
namespace Avalonia.Controls.Primitives
{
///
@@ -12,7 +14,13 @@ namespace Avalonia.Controls.Primitives
/// Defines the property.
///
public static readonly StyledProperty