From 637b3806cb55957a8c1e3e107a1b7a04e12a5ad7 Mon Sep 17 00:00:00 2001
From: Marcel Wagner <marcel.alex.wagner@outlook.com>
Date: Fri, 5 Jun 2020 19:40:25 +0200
Subject: [PATCH] Resize tab view items only once the pointer has left the
 TabViewItem strip (#2569)

* Add initial behavior

* Hide scroll buttons when they are not needed anymore

* Add test

* Remove comment

* Switch to tabstrip instead of listview for pointer exited event

* Resize tabs none the less if last item got removed

* Keep buttons visible but disabled, CR feedback

* Update behavior again, now aligned with edge

* Fix test failure

* Move pointer to different UI element in test
---
 dev/TabView/InteractionTests/TabViewTests.cs  | 105 +++++++++++
 dev/TabView/TabView.cpp                       | 169 +++++++++++++-----
 dev/TabView/TabView.h                         |   9 +-
 dev/TabView/TestUI/TabViewPage.xaml           |   7 +-
 dev/TabView/TestUI/TabViewPage.xaml.cs        |   8 +-
 .../TestUI/TabViewTabClosingBehaviorPage.xaml |  44 +++++
 .../TabViewTabClosingBehaviorPage.xaml.cs     |  72 ++++++++
 dev/TabView/TestUI/TabView_TestUI.projitems   |   7 +
 8 files changed, 374 insertions(+), 47 deletions(-)
 create mode 100644 dev/TabView/TestUI/TabViewTabClosingBehaviorPage.xaml
 create mode 100644 dev/TabView/TestUI/TabViewTabClosingBehaviorPage.xaml.cs

diff --git a/dev/TabView/InteractionTests/TabViewTests.cs b/dev/TabView/InteractionTests/TabViewTests.cs
index 8cb28a8792..2b4d6e2110 100644
--- a/dev/TabView/InteractionTests/TabViewTests.cs
+++ b/dev/TabView/InteractionTests/TabViewTests.cs
@@ -161,10 +161,14 @@ public void TabSizeAndScrollButtonsTest()
                 // Close a tab to make room. The scroll buttons should disappear:
                 Log.Comment("Closing a tab:");
                 Button closeButton = FindCloseButton(FindElement.ByName("LongHeaderTab"));
+                closeButton.MovePointer(0, 0);
                 closeButton.InvokeAndWait();
                 VerifyElement.NotFound("LongHeaderTab", FindBy.Name);
 
                 Log.Comment("Scroll buttons should disappear");
+                // Leaving tabstrip with this so the tabs update their width
+                FindElement.ByName<Button>("IsClosableCheckBox").MovePointer(0,0);
+                Wait.ForIdle();
                 Verify.IsFalse(AreScrollButtonsVisible(), "Scroll buttons should disappear");
 
                 // Make sure the scroll buttons can show up in 'Equal' sizing mode. 
@@ -647,6 +651,107 @@ public void VerifyTabViewItemHeaderForegroundResource()
             }
         }
 
+        [TestMethod]
+        public void VerifySizingBehaviorOnTabCloseComingFromScroll()
+        {
+            int pixelTolerance = 10;
+
+            using (var setup = new TestSetupHelper(new[] { "TabView Tests", "TabViewTabClosingBehaviorButton" }))
+            {
+
+                Log.Comment("Verifying sizing behavior when closing a tab");
+                CloseTabAndVerifyWidth("Tab 1", 500, "True;False;");
+
+                CloseTabAndVerifyWidth("Tab 2", 500, "True;False;");
+
+                CloseTabAndVerifyWidth("Tab 3", 500, "False;False;");
+
+                CloseTabAndVerifyWidth("Tab 5", 401, "False;False;");
+
+                CloseTabAndVerifyWidth("Tab 4", 401, "False;False;");
+
+                Log.Comment("Leaving the pointer exited area");
+                var readTabViewWidthButton = new Button(FindElement.ByName("GetActualWidthButton"));
+                readTabViewWidthButton.Click();
+                Wait.ForIdle();
+
+                readTabViewWidthButton.Click();
+                Wait.ForIdle();
+
+                Log.Comment("Verify correct TabView width");
+                Verify.IsTrue(Math.Abs(GetActualTabViewWidth() - 283) < pixelTolerance);
+            }
+
+            void CloseTabAndVerifyWidth(string tabName, int expectedValue, string expectedScrollbuttonStates)
+            {
+                Log.Comment("Closing tab:" + tabName);
+                FindCloseButton(FindElement.ByName(tabName)).Click();
+                Wait.ForIdle();
+                Log.Comment("Verifying TabView width");
+                Verify.IsTrue(Math.Abs(GetActualTabViewWidth() - expectedValue) < pixelTolerance);
+                Verify.AreEqual(expectedScrollbuttonStates, FindElement.ByName("ScrollButtonStatus").GetText());
+
+            }
+
+            double GetActualTabViewWidth()
+            {
+                var tabviewWidth = new TextBlock(FindElement.ByName("TabViewWidth"));
+
+                return Double.Parse(tabviewWidth.GetText());
+            }
+        }
+
+        [TestMethod]
+        public void VerifySizingBehaviorOnTabCloseComingFromNonScroll()
+        {
+            int pixelTolerance = 10;
+
+            using (var setup = new TestSetupHelper(new[] { "TabView Tests", "TabViewTabClosingBehaviorButton" }))
+            {
+
+                Log.Comment("Verifying sizing behavior when closing a tab");
+                CloseTabAndVerifyWidth("Tab 1", 500, "True;False;");
+
+                CloseTabAndVerifyWidth("Tab 2", 500, "True;False;");
+
+                CloseTabAndVerifyWidth("Tab 3", 500, "False;False;");
+
+                var readTabViewWidthButton = new Button(FindElement.ByName("GetActualWidthButton"));
+                readTabViewWidthButton.Click();
+                Wait.ForIdle();
+
+                CloseTabAndVerifyWidth("Tab 5", 500, "False;False;");
+
+                CloseTabAndVerifyWidth("Tab 4", 500, "False;False;");
+
+                Log.Comment("Leaving the pointer exited area");
+
+                readTabViewWidthButton.Click();
+                Wait.ForIdle();
+
+                Log.Comment("Verify correct TabView width");
+                Verify.IsTrue(Math.Abs(GetActualTabViewWidth() - 500) < pixelTolerance);
+            }
+
+            void CloseTabAndVerifyWidth(string tabName, int expectedValue, string expectedScrollbuttonStates)
+            {
+                Log.Comment("Closing tab:" + tabName);
+                FindCloseButton(FindElement.ByName(tabName)).Click();
+                Wait.ForIdle();
+                Log.Comment("Verifying TabView width");
+                Verify.IsTrue(Math.Abs(GetActualTabViewWidth() - expectedValue) < pixelTolerance);
+                Verify.AreEqual(expectedScrollbuttonStates, FindElement.ByName("ScrollButtonStatus").GetText());
+
+            }
+
+            double GetActualTabViewWidth()
+            {
+                var tabviewWidth = new TextBlock(FindElement.ByName("TabViewWidth"));
+
+                return Double.Parse(tabviewWidth.GetText());
+            }
+        }
+
         public void PressButtonAndVerifyText(String buttonName, String textBlockName, String expectedText)
         {
             Button button = FindElement.ByName<Button>(buttonName);
diff --git a/dev/TabView/TabView.cpp b/dev/TabView/TabView.cpp
index 3bc3565eb6..f37b6e9f8f 100644
--- a/dev/TabView/TabView.cpp
+++ b/dev/TabView/TabView.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License. See LICENSE in the project root for license information.
 
 #include "pch.h"
@@ -80,7 +80,15 @@ void TabView::OnApplyTemplate()
     m_addButtonColumn.set(GetTemplateChildT<winrt::ColumnDefinition>(L"AddButtonColumn", controlProtected));
     m_rightContentColumn.set(GetTemplateChildT<winrt::ColumnDefinition>(L"RightContentColumn", controlProtected));
 
-    m_tabContainerGrid.set(GetTemplateChildT<winrt::Grid>(L"TabContainerGrid", controlProtected));
+    if (const auto& containerGrid = GetTemplateChildT<winrt::Grid>(L"TabContainerGrid", controlProtected))
+    {
+        m_tabContainerGrid.set(containerGrid);
+        m_tabStripPointerExitedRevoker = containerGrid.PointerExited(winrt::auto_revoke, { this,&TabView::OnTabStripPointerExited });
+    }
+    else
+    {
+        m_tabContainerGrid.set(nullptr);
+    }
 
     m_shadowReceiver.set(GetTemplateChildT<winrt::Grid>(L"ShadowReceiver", controlProtected));
 
@@ -341,6 +349,18 @@ void TabView::OnListViewLoaded(const winrt::IInspectable&, const winrt::RoutedEv
     }
 }
 
+void TabView::OnTabStripPointerExited(const winrt::IInspectable& sender, const winrt::PointerRoutedEventArgs& args)
+{
+    if (updateTabWidthOnPointerLeave)
+    {
+        auto scopeGuard = gsl::finally([this]()
+        {
+            updateTabWidthOnPointerLeave = false;
+        });
+        UpdateTabWidths();
+    }
+}
+
 void TabView::OnScrollViewerLoaded(const winrt::IInspectable&, const winrt::RoutedEventArgs& args)
 {
     if (auto&& scrollViewer = m_scrollViewer.get())
@@ -423,8 +443,12 @@ void TabView::UpdateScrollViewerDecreaseAndIncreaseButtonsViewState()
 
 void TabView::OnItemsPresenterSizeChanged(const winrt::IInspectable& sender, const winrt::SizeChangedEventArgs& args)
 {
-    UpdateTabWidths();
-    UpdateScrollViewerDecreaseAndIncreaseButtonsViewState();
+    if (!updateTabWidthOnPointerLeave)
+    {
+        // Presenter size didn't change because of item being removed, so update manually
+        UpdateScrollViewerDecreaseAndIncreaseButtonsViewState();
+        UpdateTabWidths();
+    }
 }
 
 void TabView::OnItemsChanged(winrt::IInspectable const& item)
@@ -434,42 +458,61 @@ void TabView::OnItemsChanged(winrt::IInspectable const& item)
         m_tabItemsChangedEventSource(*this, args);
 
         int numItems = static_cast<int>(TabItems().Size());
-        if (args.CollectionChange() == winrt::CollectionChange::ItemRemoved && numItems > 0)
+
+        if (args.CollectionChange() == winrt::CollectionChange::ItemRemoved)
         {
-            // SelectedIndex might also already be -1
-            auto selectedIndex = SelectedIndex();
-            if (selectedIndex == -1 || selectedIndex == static_cast<int32_t>(args.Index()))
+            updateTabWidthOnPointerLeave = true;
+            if (numItems > 0)
             {
-                // Find the closest tab to select instead.
-                int startIndex = static_cast<int>(args.Index());
-                if (startIndex >= numItems)
+                // SelectedIndex might also already be -1
+                auto selectedIndex = SelectedIndex();
+                if (selectedIndex == -1 || selectedIndex == static_cast<int32_t>(args.Index()))
                 {
-                    startIndex = numItems - 1;
-                }
-                int index = startIndex;
-
-                do
-                {
-                    auto nextItem = ContainerFromIndex(index).as<winrt::ListViewItem>();
-
-                    if (nextItem && nextItem.IsEnabled() && nextItem.Visibility() == winrt::Visibility::Visible)
+                    // Find the closest tab to select instead.
+                    int startIndex = static_cast<int>(args.Index());
+                    if (startIndex >= numItems)
                     {
-                        SelectedItem(TabItems().GetAt(index));
-                        break;
+                        startIndex = numItems - 1;
                     }
+                    int index = startIndex;
 
-                    // try the next item
-                    index++;
-                    if (index >= numItems)
+                    do
                     {
-                        index = 0;
-                    }
-                } while (index != startIndex);
+                        auto nextItem = ContainerFromIndex(index).as<winrt::ListViewItem>();
+
+                        if (nextItem && nextItem.IsEnabled() && nextItem.Visibility() == winrt::Visibility::Visible)
+                        {
+                            SelectedItem(TabItems().GetAt(index));
+                            break;
+                        }
+
+                        // try the next item
+                        index++;
+                        if (index >= numItems)
+                        {
+                            index = 0;
+                        }
+                    } while (index != startIndex);
+                }
+
+            }
+            // Last item removed, update sizes
+            // The index of the last element is "Size() - 1", but in TabItems, it is already removed.
+            if (TabWidthMode() == winrt::TabViewWidthMode::Equal)
+            {
+                updateTabWidthOnPointerLeave = true;
+                if (args.Index() == TabItems().Size())
+                {
+                    UpdateTabWidths(true,false);
+                }
             }
         }
+        else
+        {
+            UpdateTabWidths();
+        }
     }
 
-    UpdateTabWidths();
 }
 
 void TabView::OnListViewSelectionChanged(const winrt::IInspectable& sender, const winrt::SelectionChangedEventArgs& args)
@@ -620,6 +663,7 @@ void TabView::RequestCloseTab(winrt::TabViewItem const& container)
             internalTabViewItem->RaiseRequestClose(*args);
         }
     }
+    UpdateTabWidths(false);
 }
 
 void TabView::OnScrollDecreaseClick(const winrt::IInspectable&, const winrt::RoutedEventArgs&)
@@ -649,7 +693,7 @@ winrt::Size TabView::MeasureOverride(winrt::Size const& availableSize)
     return __super::MeasureOverride(availableSize);
 }
 
-void TabView::UpdateTabWidths()
+void TabView::UpdateTabWidths(bool shouldUpdateWidths,bool fillAllAvailableSpace)
 {
     double tabWidth = std::numeric_limits<double>::quiet_NaN();
 
@@ -685,14 +729,41 @@ void TabView::UpdateTabWidths()
             {
                 if (TabWidthMode() == winrt::TabViewWidthMode::Equal)
                 {
+
                     auto const minTabWidth = unbox_value<double>(SharedHelpers::FindInApplicationResources(c_tabViewItemMinWidthName, box_value(c_tabMinimumWidth)));
                     auto const maxTabWidth = unbox_value<double>(SharedHelpers::FindInApplicationResources(c_tabViewItemMaxWidthName, box_value(c_tabMaximumWidth)));
 
-                    // Calculate the proportional width of each tab given the width of the ScrollViewer.
+                    // If we should fill all of the available space, use scrollviewer dimensions
                     auto const padding = Padding();
-                    auto const tabWidthForScroller = (availableWidth - (padding.Left + padding.Right)) / (double)(TabItems().Size());
+                    if (fillAllAvailableSpace)
+                    {
+                        // Calculate the proportional width of each tab given the width of the ScrollViewer.
+                        auto const tabWidthForScroller = (availableWidth - (padding.Left + padding.Right)) / (double)(TabItems().Size());
+                        tabWidth = std::clamp(tabWidthForScroller, minTabWidth, maxTabWidth);
+                    }
+                    else
+                    {
+                        double availableTabViewSpace = (tabColumn.ActualWidth() - (padding.Left + padding.Right));
+                        if (const auto increaseButton = m_scrollIncreaseButton.get())
+                        {
+                            if (increaseButton.Visibility() == winrt::Visibility::Visible)
+                            {
+                                availableTabViewSpace -= increaseButton.ActualWidth();
+                            }
+                        }
+
+                        if (const auto decreaseButton = m_scrollDecreaseButton.get())
+                        {
+                            if (decreaseButton.Visibility() == winrt::Visibility::Visible)
+                            {
+                                availableTabViewSpace -= decreaseButton.ActualWidth();
+                            }
+                        }
+
+                        // Use current size to update items to fill the currently occupied space
+                        tabWidth = availableTabViewSpace / (double)(TabItems().Size());
+                    }
 
-                    tabWidth = std::clamp(tabWidthForScroller, minTabWidth, maxTabWidth);
 
                     // Size tab column to needed size
                     tabColumn.MaxWidth(availableWidth);
@@ -711,7 +782,15 @@ void TabView::UpdateTabWidths()
                         tabColumn.Width(winrt::GridLengthHelper::FromValueAndType(1.0, winrt::GridUnitType::Auto));
                         if (auto listview = m_listView.get())
                         {
-                            winrt::FxScrollViewer::SetHorizontalScrollBarVisibility(listview, winrt::Windows::UI::Xaml::Controls::ScrollBarVisibility::Hidden);
+                            if (shouldUpdateWidths && fillAllAvailableSpace)
+                            {
+                                winrt::FxScrollViewer::SetHorizontalScrollBarVisibility(listview, winrt::Windows::UI::Xaml::Controls::ScrollBarVisibility::Hidden);
+                            }
+                            else
+                            {
+                                m_scrollDecreaseButton.get().IsEnabled(false);
+                                m_scrollIncreaseButton.get().IsEnabled(false);
+                            }
                         }
                     }
                 }
@@ -742,18 +821,22 @@ void TabView::UpdateTabWidths()
         }
     }
 
-    for (auto item : TabItems())
+
+    if (shouldUpdateWidths || TabWidthMode() != winrt::TabViewWidthMode::Equal)
     {
-        // Set the calculated width on each tab.
-        auto tvi = item.try_as<winrt::TabViewItem>();
-        if (!tvi)
+        for (auto item : TabItems())
         {
-            tvi = ContainerFromItem(item).as<winrt::TabViewItem>();
-        }
+            // Set the calculated width on each tab.
+            auto tvi = item.try_as<winrt::TabViewItem>();
+            if (!tvi)
+            {
+                tvi = ContainerFromItem(item).as<winrt::TabViewItem>();
+            }
 
-        if (tvi)
-        {
-            tvi.Width(tabWidth);
+            if (tvi)
+            {
+                tvi.Width(tabWidth);
+            }
         }
     }
 }
diff --git a/dev/TabView/TabView.h b/dev/TabView/TabView.h
index 0a81378f19..ffe3b5fde0 100644
--- a/dev/TabView/TabView.h
+++ b/dev/TabView/TabView.h
@@ -89,7 +89,7 @@ class TabView :
 
     // IFrameworkElement
     void OnApplyTemplate();
-    winrt::Size MeasureOverride(winrt::Size const& availableSize); 
+    winrt::Size MeasureOverride(winrt::Size const& availableSize);
 
     // IUIElement
     winrt::AutomationPeer OnCreateAutomationPeer();
@@ -127,6 +127,7 @@ class TabView :
     void OnItemsPresenterSizeChanged(const winrt::IInspectable& sender, const winrt::SizeChangedEventArgs& args);
 
     void OnListViewLoaded(const winrt::IInspectable& sender, const winrt::RoutedEventArgs& args);
+    void OnTabStripPointerExited(const winrt::IInspectable& sender, const winrt::PointerRoutedEventArgs& args);
     void OnListViewSelectionChanged(const winrt::IInspectable& sender, const winrt::SelectionChangedEventArgs& args);
 
     void OnListViewDragItemsStarting(const winrt::IInspectable& sender, const winrt::DragItemsStartingEventArgs& args);
@@ -144,7 +145,7 @@ class TabView :
     void UpdateSelectedItem();
     void UpdateSelectedIndex();
 
-    void UpdateTabWidths();
+    void UpdateTabWidths(bool shouldUpdateWidths=true, bool fillAllAvailableSpace=true);
 
     void UpdateScrollViewerDecreaseAndIncreaseButtonsViewState();
 
@@ -152,6 +153,9 @@ class TabView :
 
     int GetItemCount();
 
+    bool updateTabWidthOnPointerLeave{ false };
+
+
     winrt::TabViewItem FindTabViewItemFromDragItem(const winrt::IInspectable& item);
 
     tracker_ref<winrt::ColumnDefinition> m_leftContentColumn{ this };
@@ -172,6 +176,7 @@ class TabView :
     tracker_ref<winrt::Grid> m_shadowReceiver{ this };
 
     winrt::ListView::Loaded_revoker m_listViewLoadedRevoker{};
+    winrt::ListView::PointerExited_revoker m_tabStripPointerExitedRevoker{};
     winrt::Selector::SelectionChanged_revoker m_listViewSelectionChangedRevoker{};
     winrt::UIElement::GettingFocus_revoker m_listViewGettingFocusRevoker{};
 
diff --git a/dev/TabView/TestUI/TabViewPage.xaml b/dev/TabView/TestUI/TabViewPage.xaml
index 4a1dc0d9f2..abf9084f67 100644
--- a/dev/TabView/TestUI/TabViewPage.xaml
+++ b/dev/TabView/TestUI/TabViewPage.xaml
@@ -153,7 +153,12 @@
 
                         <StackPanel x:Name="FirstTabContent" AutomationProperties.Name="FirstTabContent">
                             <Button x:Name="FirstTabButton" AutomationProperties.Name="FirstTabButton" Margin="8" FontSize="20">Home Button</Button>
-                            <Button x:Name="TabViewSizingPageButton" AutomationProperties.Name="TabViewSizingPageButton" Margin="8" Click="TabViewSizingPageButton_Click" FontSize="20">TabView Sizing Page</Button>
+                            <Button x:Name="TabViewSizingPageButton" AutomationProperties.Name="TabViewSizingPageButton"
+                                    Margin="8" Click="TabViewSizingPageButton_Click"
+                                    FontSize="20">TabView Sizing Page</Button>
+                            <Button x:Name="TabViewTabClosingBehaviorButton" AutomationProperties.Name="TabViewTabClosingBehaviorButton"
+                                    Margin="8" Click="TabViewTabClosingBehaviorButton_Click"
+                                    FontSize="20">TabView Tab Closing behavior Page</Button>
                         </StackPanel>
                     </controls:TabViewItem>
 
diff --git a/dev/TabView/TestUI/TabViewPage.xaml.cs b/dev/TabView/TestUI/TabViewPage.xaml.cs
index 8e0a5f652d..a9861bd8e5 100644
--- a/dev/TabView/TestUI/TabViewPage.xaml.cs
+++ b/dev/TabView/TestUI/TabViewPage.xaml.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License. See LICENSE in the project root for license information.
 
 using System;
@@ -394,6 +394,12 @@ private void TabViewSizingPageButton_Click(object sender, RoutedEventArgs e)
             this.Frame.Navigate(typeof(TabViewSizingPage));
         }
 
+        
+        private void TabViewTabClosingBehaviorButton_Click(object sender, RoutedEventArgs e)
+        {
+            this.Frame.Navigate(typeof(TabViewTabClosingBehaviorPage));
+        }
+
         private void ShortLongTextButton_Click(object sender, RoutedEventArgs e)
         {
             FirstTab.Header = "s";
diff --git a/dev/TabView/TestUI/TabViewTabClosingBehaviorPage.xaml b/dev/TabView/TestUI/TabViewTabClosingBehaviorPage.xaml
new file mode 100644
index 0000000000..f39f3a7f7d
--- /dev/null
+++ b/dev/TabView/TestUI/TabViewTabClosingBehaviorPage.xaml
@@ -0,0 +1,44 @@
+<Page
+    x:Class="MUXControlsTestApp.TabViewTabClosingBehaviorPage"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
+    mc:Ignorable="d"
+    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
+    <Grid>
+        <StackPanel Orientation="Horizontal">
+            <Grid MaxWidth="500">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+                <muxc:TabView
+                    MaxWidth="500"
+                    x:Name="Tabs"
+                    TabCloseRequested="TabViewTabCloseRequested"
+                    AddTabButtonClick="AddButtonClick">
+
+                    <muxc:TabViewItem Header="Tab 0" AutomationProperties.Name="Tab 0"/>
+                    <muxc:TabViewItem Header="Tab 1" AutomationProperties.Name="Tab 1"/>
+                    <muxc:TabViewItem Header="Tab 2" AutomationProperties.Name="Tab 2"/>
+                    <muxc:TabViewItem Header="Tab 3" AutomationProperties.Name="Tab 3"/>
+                    <muxc:TabViewItem Header="Tab 4" AutomationProperties.Name="Tab 4"/>
+                    <muxc:TabViewItem Header="Tab 5" AutomationProperties.Name="Tab 5"/>
+                </muxc:TabView>
+            </Grid>
+            <StackPanel>
+                <TextBlock Text="Actual width"/>
+                <TextBlock x:Name="TabViewWidth" AutomationProperties.Name="TabViewWidth"/>
+                <TextBlock Text="Scroll buttons status"/>
+                <TextBlock x:Name="ScrollButtonStatus" AutomationProperties.Name="ScrollButtonStatus" />
+            </StackPanel>
+            <StackPanel>
+                <Button AutomationProperties.Name="GetActualWidthButton"
+                        Click="GetActualWidthsButton_Click"
+                        Content="Get actual TabView width"/>
+            </StackPanel>
+        </StackPanel>
+    </Grid>
+</Page>
diff --git a/dev/TabView/TestUI/TabViewTabClosingBehaviorPage.xaml.cs b/dev/TabView/TestUI/TabViewTabClosingBehaviorPage.xaml.cs
new file mode 100644
index 0000000000..dd8b3aa58b
--- /dev/null
+++ b/dev/TabView/TestUI/TabViewTabClosingBehaviorPage.xaml.cs
@@ -0,0 +1,72 @@
+using Microsoft.UI.Xaml.Controls;
+using MUXControlsTestApp.Utilities;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+
+// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
+
+namespace MUXControlsTestApp
+{
+    /// <summary>
+    /// An empty page that can be used on its own or navigated to within a Frame.
+    /// </summary>
+    public sealed partial class TabViewTabClosingBehaviorPage : Page
+    {
+        private int _newTabNumber = 0;
+
+        public TabViewTabClosingBehaviorPage()
+        {
+            this.InitializeComponent();
+        }
+
+        public void AddButtonClick(object sender, object e)
+        {
+            if (Tabs != null)
+            {
+                TabViewItem item = new TabViewItem();
+                item.Header = "New Tab " + _newTabNumber;
+                item.Content = item.Header;
+
+                Tabs.TabItems.Add(item);
+
+                _newTabNumber++;
+            }
+        }
+
+        private void TabViewTabCloseRequested(object sender, Microsoft.UI.Xaml.Controls.TabViewTabCloseRequestedEventArgs e)
+        {
+            Tabs.TabItems.Remove(e.Tab);
+
+            TabViewWidth.Text = Tabs.ActualWidth.ToString();
+
+            var scrollButtonStateValue = "";
+
+            var scrollIncreaseButton = VisualTreeUtils.FindVisualChildByName(Tabs, "ScrollIncreaseButton") as RepeatButton;
+            var scrollDecreaseButton = VisualTreeUtils.FindVisualChildByName(Tabs, "ScrollDecreaseButton") as RepeatButton;
+
+            scrollButtonStateValue += scrollIncreaseButton.IsEnabled + ";";
+            scrollButtonStateValue += scrollDecreaseButton.IsEnabled + ";";
+
+            ScrollButtonStatus.Text = scrollButtonStateValue;
+        }
+
+        public void GetActualWidthsButton_Click(object sender, RoutedEventArgs e)
+        {
+            // This is the smallest width that fits our content without any scrolling.
+            TabViewWidth.Text = Tabs.ActualWidth.ToString();
+        }
+
+    }
+}
diff --git a/dev/TabView/TestUI/TabView_TestUI.projitems b/dev/TabView/TestUI/TabView_TestUI.projitems
index 4824c63692..cc36bc2a09 100644
--- a/dev/TabView/TestUI/TabView_TestUI.projitems
+++ b/dev/TabView/TestUI/TabView_TestUI.projitems
@@ -18,6 +18,10 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="$(MSBuildThisFileDirectory)TabViewTabClosingBehaviorPage.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
   </ItemGroup>
   <ItemGroup>
     <Compile Include="$(MSBuildThisFileDirectory)TabViewSizingPage.xaml.cs">
@@ -26,5 +30,8 @@
     <Compile Include="$(MSBuildThisFileDirectory)TabViewPage.xaml.cs">
       <DependentUpon>TabViewPage.xaml</DependentUpon>
     </Compile>
+    <Compile Include="$(MSBuildThisFileDirectory)TabViewTabClosingBehaviorPage.xaml.cs">
+      <DependentUpon>TabViewTabClosingBehaviorPage.xaml</DependentUpon>
+    </Compile>
   </ItemGroup>
 </Project>
\ No newline at end of file