Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resize tab view items only once the pointer has left the TabViewItem strip #2569

Merged
merged 10 commits into from
Jun 5, 2020
55 changes: 55 additions & 0 deletions dev/TabView/InteractionTests/TabViewTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,61 @@ public void VerifyTabViewItemHeaderForegroundResource()
}
}

[TestMethod]
public void VerifySizingBehaviorOnTabClose()
{
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);

CloseTabAndVerifyWidth("Tab 2", 500, true);

CloseTabAndVerifyWidth("Tab 3", 443, false);

CloseTabAndVerifyWidth("Tab 4", 343, 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() - 500) < pixelTolerance);
}

void CloseTabAndVerifyWidth(string tabName, int expectedValue, bool expectedScrollButtonVisible)
{
Log.Comment("Closing tab:" + tabName);
FindCloseButton(FindElement.ByName(tabName)).Click();
Wait.ForIdle();
Log.Comment("Verifying TabView width");
Verify.IsTrue(Math.Abs(GetActualTabViewWidth() - expectedValue) < pixelTolerance);
if(expectedScrollButtonVisible)
{
Verify.AreEqual("true;true;", FindElement.ByName("ScrollButtonStatus").GetText());
}
else
{
Verify.AreEqual("false;false;", 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);
Expand Down
103 changes: 66 additions & 37 deletions dev/TabView/TabView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_tabContainerGrid [](start = 8, length = 18)

a downside to this approach is that you will never set m_tabContainerGrid to null, even if a second template is later applied which doesn't have a TabContainerGrid. Seems unlikely in this case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the code to set it to null if we get a nullptr.

m_tabStripPointerExitedRevoker = containerGrid.PointerExited(winrt::auto_revoke, { this,&TabView::OnTabStripPointerExited });
}

m_shadowReceiver.set(GetTemplateChildT<winrt::Grid>(L"ShadowReceiver", controlProtected));

Expand Down Expand Up @@ -341,6 +345,18 @@ void TabView::OnListViewLoaded(const winrt::IInspectable&, const winrt::RoutedEv
}
}

void TabView::OnTabStripPointerExited(const winrt::IInspectable& sender, const winrt::PointerRoutedEventArgs& args)
{
if (updateTabWidthOnPointerLeave)
{
UpdateTabWidths();
auto scopeGuard = gsl::finally([this]()
{
updateTabWidthOnPointerLeave = false;
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scope guarded flag should be declared before UpdateTabWidths() is called. This is so that if UpdateTabWidths() throws but that exception is later handled we will still update this flag.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, moved the UpdateTabWidths call behind the scopeguard.

}
}

void TabView::OnScrollViewerLoaded(const winrt::IInspectable&, const winrt::RoutedEventArgs& args)
{
if (auto&& scrollViewer = m_scrollViewer.get())
Expand Down Expand Up @@ -423,7 +439,7 @@ void TabView::UpdateScrollViewerDecreaseAndIncreaseButtonsViewState()

void TabView::OnItemsPresenterSizeChanged(const winrt::IInspectable& sender, const winrt::SizeChangedEventArgs& args)
{
UpdateTabWidths();
updateTabWidthOnPointerLeave = true;
UpdateScrollViewerDecreaseAndIncreaseButtonsViewState();
}

Expand All @@ -434,42 +450,50 @@ 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);
}
}
}
else
{
UpdateTabWidths();
}
}

UpdateTabWidths();
}

void TabView::OnListViewSelectionChanged(const winrt::IInspectable& sender, const winrt::SelectionChangedEventArgs& args)
Expand Down Expand Up @@ -620,6 +644,7 @@ void TabView::RequestCloseTab(winrt::TabViewItem const& container)
internalTabViewItem->RaiseRequestClose(*args);
}
}
UpdateTabWidths(false);
}

void TabView::OnScrollDecreaseClick(const winrt::IInspectable&, const winrt::RoutedEventArgs&)
Expand Down Expand Up @@ -649,7 +674,7 @@ winrt::Size TabView::MeasureOverride(winrt::Size const& availableSize)
return __super::MeasureOverride(availableSize);
}

void TabView::UpdateTabWidths()
void TabView::UpdateTabWidths(bool shouldUpdateWidths)
{
double tabWidth = std::numeric_limits<double>::quiet_NaN();

Expand Down Expand Up @@ -742,18 +767,22 @@ void TabView::UpdateTabWidths()
}
}

for (auto item : TabItems())

if (shouldUpdateWidths)
{
// 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);
}
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions dev/TabView/TabView.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -144,14 +145,17 @@ class TabView :
void UpdateSelectedItem();
void UpdateSelectedIndex();

void UpdateTabWidths();
void UpdateTabWidths(bool shouldUpdateWidths=true);

void UpdateScrollViewerDecreaseAndIncreaseButtonsViewState();

void OnListViewGettingFocus(const winrt::IInspectable& sender, const winrt::GettingFocusEventArgs& args);

int GetItemCount();

bool updateTabWidthOnPointerLeave{ false };


winrt::TabViewItem FindTabViewItemFromDragItem(const winrt::IInspectable& item);

tracker_ref<winrt::ColumnDefinition> m_leftContentColumn{ this };
Expand All @@ -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{};

Expand Down
7 changes: 6 additions & 1 deletion dev/TabView/TestUI/TabViewPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
8 changes: 7 additions & 1 deletion dev/TabView/TestUI/TabViewPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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";
Expand Down
44 changes: 44 additions & 0 deletions dev/TabView/TestUI/TabViewTabClosingBehaviorPage.xaml
Original file line number Diff line number Diff line change
@@ -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>
Loading