-
Notifications
You must be signed in to change notification settings - Fork 683
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
Changes from 8 commits
01f31c4
dd78553
85d99b9
41e2f25
4c03af4
ceb4c77
a587fd2
89b02d3
ebb74f2
d94dc69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, moved the UpdateTabWidths call behind the scopeguard. |
||
UpdateTabWidths(); | ||
} | ||
} | ||
|
||
void TabView::OnScrollViewerLoaded(const winrt::IInspectable&, const winrt::RoutedEventArgs& args) | ||
{ | ||
if (auto&& scrollViewer = m_scrollViewer.get()) | ||
|
@@ -423,8 +443,11 @@ 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(); | ||
} | ||
} | ||
|
||
void TabView::OnItemsChanged(winrt::IInspectable const& item) | ||
|
@@ -434,42 +457,57 @@ 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 (args.Index() == TabItems().Size()) | ||
{ | ||
UpdateTabWidths(true,false); | ||
} | ||
} | ||
else | ||
{ | ||
UpdateTabWidths(); | ||
} | ||
} | ||
|
||
UpdateTabWidths(); | ||
} | ||
|
||
void TabView::OnListViewSelectionChanged(const winrt::IInspectable& sender, const winrt::SelectionChangedEventArgs& args) | ||
|
@@ -620,6 +658,7 @@ void TabView::RequestCloseTab(winrt::TabViewItem const& container) | |
internalTabViewItem->RaiseRequestClose(*args); | ||
} | ||
} | ||
UpdateTabWidths(false); | ||
} | ||
|
||
void TabView::OnScrollDecreaseClick(const winrt::IInspectable&, const winrt::RoutedEventArgs&) | ||
|
@@ -649,7 +688,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 +724,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 +777,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); | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -731,7 +805,7 @@ void TabView::UpdateTabWidths() | |
winrt::FxScrollViewer::SetHorizontalScrollBarVisibility(listview, visible | ||
? winrt::Windows::UI::Xaml::Controls::ScrollBarVisibility::Visible | ||
: winrt::Windows::UI::Xaml::Controls::ScrollBarVisibility::Hidden); | ||
if (visible) | ||
if (visible && shouldUpdateWidths) | ||
{ | ||
UpdateScrollViewerDecreaseAndIncreaseButtonsViewState(); | ||
} | ||
|
@@ -742,18 +816,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); | ||
} | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
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.