Skip to content

Commit

Permalink
Delay load Hierarchical NavViewItem elements (#6808)
Browse files Browse the repository at this point in the history
* test code

* delay load chevron

* fix delay load chevron

* delay load NVI MenuItemsHost

* added visual verification test

* updated visual verification files

* updated chevron delay load

* delete test code

* added generated visual verification files

* addressed comments
  • Loading branch information
ojhad authored Mar 12, 2022
1 parent 4e2990d commit d3fef08
Show file tree
Hide file tree
Showing 23 changed files with 16,028 additions and 3,685 deletions.
1 change: 1 addition & 0 deletions dev/NavigationView/NavigationView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@
Control.IsTemplateFocusTarget="True">
</primitives:NavigationViewItemPresenter>
<local:ItemsRepeater
x:Load="False"
Grid.Row="1"
Visibility="Collapsed"
x:Name="NavigationViewItemMenuItemsHost">
Expand Down
168 changes: 115 additions & 53 deletions dev/NavigationView/NavigationViewItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,9 @@ void NavigationViewItem::OnApplyTemplate()
Loaded({ this, &NavigationViewItem::OnLoaded });
}

// Retrieve reference to NavigationView
if (auto nvImpl = winrt::get_self<NavigationView>(GetNavigationView()))
if (HasPotentialChildren())
{
if (auto repeater = GetTemplateChildT<winrt::ItemsRepeater>(c_repeater, controlProtected))
{
m_repeater.set(repeater);

// Primary element setup happens in NavigationView
m_repeaterElementPreparedRevoker = repeater.ElementPrepared(winrt::auto_revoke, { nvImpl, &NavigationView::OnRepeaterElementPrepared });
m_repeaterElementClearingRevoker = repeater.ElementClearing(winrt::auto_revoke, { nvImpl, &NavigationView::OnRepeaterElementClearing });

repeater.ItemTemplate(*(nvImpl->GetNavigationViewItemsFactory()));
}

LoadMenuItemsHost();
UpdateRepeaterItemsSource();
}

Expand All @@ -140,6 +129,42 @@ void NavigationViewItem::OnApplyTemplate()
NavigationView::CreateAndAttachHeaderAnimation(visual);
}

void NavigationViewItem::LoadElementsForDisplayingChildren()
{
m_hasHadChildren = true;

LoadMenuItemsHost();

if (auto nvip = GetPresenter())
{
nvip->LoadChevron();
UpdateVisualStateForChevron();
}
}

void NavigationViewItem::LoadMenuItemsHost()
{
// verify repeater is not already loaded
if (m_repeater != nullptr)
{
return;
}

if (auto nvImpl = winrt::get_self<NavigationView>(GetNavigationView()))
{
if (auto repeater = GetTemplateChildT<winrt::ItemsRepeater>(c_repeater, *this))
{
m_repeater.set(repeater);

// Primary element setup happens in NavigationView
m_repeaterElementPreparedRevoker = repeater.ElementPrepared(winrt::auto_revoke, { nvImpl, &NavigationView::OnRepeaterElementPrepared });
m_repeaterElementClearingRevoker = repeater.ElementClearing(winrt::auto_revoke, { nvImpl, &NavigationView::OnRepeaterElementClearing });

repeater.ItemTemplate(*(nvImpl->GetNavigationViewItemsFactory()));
}
}
}

void NavigationViewItem::OnLoaded(winrt::IInspectable const&, winrt::RoutedEventArgs const&)
{
if (auto splitView = GetSplitView())
Expand Down Expand Up @@ -295,20 +320,41 @@ void NavigationViewItem::OnInfoBadgePropertyChanged(const winrt::DependencyPrope
UpdateVisualStateForInfoBadge();
}

void NavigationViewItem::OnMenuItemsVectorChanged(const winrt::Collections::IObservableVector<winrt::IInspectable>& sender, const winrt::Collections::IVectorChangedEventArgs& args)
{
LoadElementsForDisplayingChildren();
}

void NavigationViewItem::OnMenuItemsPropertyChanged(const winrt::DependencyPropertyChangedEventArgs& args)
{
m_menuItemsVectorChangedRevoker.revoke();
if (auto menuItemsVector = MenuItems())
{
if (auto menuItemsObservableVector = menuItemsVector.as<winrt::IObservableVector<winrt::IInspectable>>())
{
m_menuItemsVectorChangedRevoker = menuItemsObservableVector.VectorChanged(winrt::auto_revoke, { this, &NavigationViewItem::OnMenuItemsVectorChanged });
}

if (menuItemsVector.Size() > 0)
{
LoadElementsForDisplayingChildren();
}
}

UpdateRepeaterItemsSource();
UpdateVisualStateForChevron();
}

void NavigationViewItem::OnMenuItemsSourcePropertyChanged(const winrt::DependencyPropertyChangedEventArgs& args)
{
LoadElementsForDisplayingChildren();
UpdateRepeaterItemsSource();
UpdateVisualStateForChevron();
}

void NavigationViewItem::OnHasUnrealizedChildrenPropertyChanged(const winrt::DependencyPropertyChangedEventArgs& args)
{
LoadElementsForDisplayingChildren();
UpdateVisualStateForChevron();
}

Expand Down Expand Up @@ -558,53 +604,59 @@ void NavigationViewItem::UpdateVisualStateForChevron()
auto const chevronState = HasChildren() && !(m_isClosedCompact && ShouldRepeaterShowInFlyout()) ? (IsExpanded() ? ChevronStateValue::ChevronVisibleOpen : ChevronStateValue::ChevronVisibleClosed) : ChevronStateValue::ChevronHidden;

auto const pointerChevronState = [this, pointerStateValue, chevronState]() {
if (chevronState == ChevronStateValue::ChevronHidden)
// This Visual State Group will load the chevron in the PointerOver & Pressed states even if it is hidden.
// In order to avoid loading the chevron when we don't need it, only execute this if we can confirm chevron is needed.
if (m_hasHadChildren)
{
if (pointerStateValue == PointerStateValue::Normal)
{
return c_normalChevronHidden;
}
else if (pointerStateValue == PointerStateValue::PointerOver)
if (chevronState == ChevronStateValue::ChevronHidden)
{
return c_pointerOverChevronHidden;
}
else if (pointerStateValue == PointerStateValue::Pressed)
{
return c_pressedChevronHidden;
}
}
else if (chevronState == ChevronStateValue::ChevronVisibleOpen)
{
if (pointerStateValue == PointerStateValue::Normal)
{
return c_normalChevronVisibleOpen;
}
else if (pointerStateValue == PointerStateValue::PointerOver)
{
return c_pointerOverChevronVisibleOpen;
}
else if (pointerStateValue == PointerStateValue::Pressed)
{
return c_pressedChevronVisibleOpen;
}
}
else if (chevronState == ChevronStateValue::ChevronVisibleClosed)
{
if (pointerStateValue == PointerStateValue::Normal)
{
return c_normalChevronVisibleClosed;
if (pointerStateValue == PointerStateValue::Normal)
{
return c_normalChevronHidden;
}
else if (pointerStateValue == PointerStateValue::PointerOver)
{
return c_pointerOverChevronHidden;
}
else if (pointerStateValue == PointerStateValue::Pressed)
{
return c_pressedChevronHidden;
}
}
else if (pointerStateValue == PointerStateValue::PointerOver)
else if (chevronState == ChevronStateValue::ChevronVisibleOpen)
{
return c_pointerOverChevronVisibleClosed;
if (pointerStateValue == PointerStateValue::Normal)
{
return c_normalChevronVisibleOpen;
}
else if (pointerStateValue == PointerStateValue::PointerOver)
{
return c_pointerOverChevronVisibleOpen;
}
else if (pointerStateValue == PointerStateValue::Pressed)
{
return c_pressedChevronVisibleOpen;
}
}
else if (pointerStateValue == PointerStateValue::Pressed)
else if (chevronState == ChevronStateValue::ChevronVisibleClosed)
{
return c_pressedChevronVisibleClosed;
if (pointerStateValue == PointerStateValue::Normal)
{
return c_normalChevronVisibleClosed;
}
else if (pointerStateValue == PointerStateValue::PointerOver)
{
return c_pointerOverChevronVisibleClosed;
}
else if (pointerStateValue == PointerStateValue::Pressed)
{
return c_pressedChevronVisibleClosed;
}
}
}
return c_normalChevronHidden;
}();

// Go to the appropriate pointerChevronState
winrt::VisualStateManager::GoToState(presenter, pointerChevronState, true);

Expand All @@ -626,9 +678,19 @@ void NavigationViewItem::UpdateVisualStateForChevron()

bool NavigationViewItem::HasChildren()
{
return MenuItems().Size() > 0
|| (MenuItemsSource() != nullptr && m_repeater != nullptr && m_repeater.get().ItemsSourceView().Count() > 0)
|| HasUnrealizedChildren();
return (MenuItems() != nullptr && MenuItems().Size() > 0) ||
(MenuItemsSource() != nullptr &&
m_repeater != nullptr &&
m_repeater.get().ItemsSourceView() != nullptr &&
m_repeater.get().ItemsSourceView().Count() > 0) ||
HasUnrealizedChildren();
}

bool NavigationViewItem::HasPotentialChildren()
{
return (MenuItems() != nullptr && MenuItems().Size() > 0) ||
MenuItemsSource() != nullptr ||
HasUnrealizedChildren();
}

bool NavigationViewItem::ShouldShowIcon()
Expand Down
9 changes: 9 additions & 0 deletions dev/NavigationView/NavigationViewItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ class NavigationViewItem :
bool IsRepeaterVisible() const;
void PropagateDepthToChildren(int depth);
bool HasChildren();
// Needed for scenarios where the ItemsRepeater is not loaded (OnApplyTemplate) therefore we cannot guarantee a non-null MenuItemsSource actually contains items
bool HasPotentialChildren();

private:
winrt::UIElement const GetPresenterOrItem() const;
Expand All @@ -82,6 +84,8 @@ class NavigationViewItem :
void OnPresenterPointerCanceled(const winrt::IInspectable& sender, const winrt::PointerRoutedEventArgs& args);
void OnPresenterPointerCaptureLost(const winrt::IInspectable& sender, const winrt::PointerRoutedEventArgs& args);
void OnIsEnabledChanged(const winrt::IInspectable& sender, const winrt::DependencyPropertyChangedEventArgs& args);
void OnMenuItemsVectorChanged(const winrt::Collections::IObservableVector<winrt::IInspectable>& sender, const winrt::Collections::IVectorChangedEventArgs& args);


void ResetTrackedPointerId();
bool IgnorePointerId(const winrt::PointerRoutedEventArgs& args);
Expand Down Expand Up @@ -120,6 +124,8 @@ class NavigationViewItem :
void UnhookEventsAndClearFields();

void PrepNavigationViewItem(const winrt::SplitView& splitView);
void LoadElementsForDisplayingChildren();
void LoadMenuItemsHost();

PropertyChanged_revoker m_splitViewIsPaneOpenChangedRevoker{};
PropertyChanged_revoker m_splitViewDisplayModeChangedRevoker{};
Expand All @@ -136,6 +142,7 @@ class NavigationViewItem :
winrt::ItemsRepeater::ElementPrepared_revoker m_repeaterElementPreparedRevoker{};
winrt::ItemsRepeater::ElementClearing_revoker m_repeaterElementClearingRevoker{};
winrt::ItemsSourceView::CollectionChanged_revoker m_itemsSourceViewCollectionChangedRevoker{};
winrt::Collections::IObservableVector<winrt::IInspectable>::VectorChanged_revoker m_menuItemsVectorChangedRevoker{};

winrt::FlyoutBase::Closing_revoker m_flyoutClosingRevoker{};
winrt::Control::IsEnabledChanged_revoker m_isEnabledChangedRevoker{};
Expand All @@ -160,4 +167,6 @@ class NavigationViewItem :
bool m_isPointerOver{ false };

bool m_isRepeaterParentedToFlyout{ false };
// used to bypass all Chevron visual state logic in order to keep it unloaded
bool m_hasHadChildren{ false };
};
23 changes: 18 additions & 5 deletions dev/NavigationView/NavigationViewItemPresenter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,14 @@ void NavigationViewItemPresenter::OnApplyTemplate()

if (auto navigationViewItem = GetNavigationViewItem())
{
if (auto const expandCollapseChevron = GetTemplateChildT<winrt::Grid>(c_expandCollapseChevron, *this))
if (navigationViewItem->HasPotentialChildren())
{
m_expandCollapseChevron.set(expandCollapseChevron);
m_expandCollapseChevronTappedToken = expandCollapseChevron.Tapped({ navigationViewItem, &NavigationViewItem::OnExpandCollapseChevronTapped });
LoadChevron();
}
navigationViewItem->UpdateVisualStateNoTransition();


// We probably switched displaymode, so restore width now, otherwise the next time we will restore is when the CompactPaneLength changes
if(const auto& navigationView = navigationViewItem->GetNavigationView())
if (const auto& navigationView = navigationViewItem->GetNavigationView())
{
if (navigationView.PaneDisplayMode() != winrt::NavigationViewPaneDisplayMode::Top)
{
Expand All @@ -59,6 +57,21 @@ void NavigationViewItemPresenter::OnApplyTemplate()
UpdateMargin();
}

void NavigationViewItemPresenter::LoadChevron()
{
if (!m_expandCollapseChevron)
{
if (auto navigationViewItem = GetNavigationViewItem())
{
if (auto const expandCollapseChevron = GetTemplateChildT<winrt::Grid>(c_expandCollapseChevron, *this))
{
m_expandCollapseChevron.set(expandCollapseChevron);
m_expandCollapseChevronTappedToken = expandCollapseChevron.Tapped({ navigationViewItem, &NavigationViewItem::OnExpandCollapseChevronTapped });
}
}
}
}

void NavigationViewItemPresenter::RotateExpandCollapseChevron(bool isExpanded)
{
if (isExpanded)
Expand Down
2 changes: 2 additions & 0 deletions dev/NavigationView/NavigationViewItemPresenter.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class NavigationViewItemPresenter:

void UpdateClosedCompactVisualState(bool isTopLevelItem, bool isClosedCompact);

void LoadChevron();

private:
NavigationViewItem * GetNavigationViewItem();
void UpdateMargin();
Expand Down
37 changes: 37 additions & 0 deletions dev/NavigationView/NavigationView_ApiTests/NavigationViewTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,39 @@ private NavigationView SetupNavigationViewPaneContent(NavigationViewPaneDisplayM
return navView;
}

private NavigationView SetupNavigationViewHierarchy(NavigationViewPaneDisplayMode paneDisplayMode = NavigationViewPaneDisplayMode.Auto)
{
NavigationView navView = null;
RunOnUIThread.Execute(() =>
{
navView = new NavigationView();
NavigationViewItem navViewItem1 = new NavigationViewItem() { Content = "NVI1" };
navViewItem1.MenuItems.Add(new NavigationViewItem() { Content = "NVI1" });
NavigationViewItem navViewItem2 = new NavigationViewItem() { Content = "NVI2" };
navView.MenuItems.Add(navViewItem1);
navView.MenuItems.Add(navViewItem2);
navView.PaneTitle = "Hierarchical NavView";
navView.IsBackButtonVisible = NavigationViewBackButtonVisible.Visible;
navView.IsSettingsVisible = true;
navView.PaneDisplayMode = paneDisplayMode;
navView.OpenPaneLength = 120.0;
navView.ExpandedModeThresholdWidth = 600.0;
navView.CompactModeThresholdWidth = 400.0;
navView.Width = 800.0;
navView.Height = 600.0;
navView.Content = "This is a simple Hierarchical Navigation View test";
Content = navView;
Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().TryEnterFullScreenMode();
});

IdleSynchronizer.Wait();
return navView;
}

[TestMethod]
public void VerifyVisualTree()
{
Expand Down Expand Up @@ -187,6 +220,10 @@ public void VerifyVisualTree()
Log.Comment($"Verify visual tree for NavigationViewTopPaneContent");
var topNavViewPaneContent = SetupNavigationViewPaneContent(NavigationViewPaneDisplayMode.Top);
visualTreeVerifier.VerifyVisualTreeNoException(root: topNavViewPaneContent, verificationFileNamePrefix: "NavigationViewTopPaneContent");

Log.Comment($"Verify visual tree for NavigationViewHierarchy");
var hierarchyNavViewPaneContent = SetupNavigationViewHierarchy(NavigationViewPaneDisplayMode.Left);
visualTreeVerifier.VerifyVisualTreeNoException(root: hierarchyNavViewPaneContent, verificationFileNamePrefix: "NavigationViewHierarchy");
}
}

Expand Down
Loading

0 comments on commit d3fef08

Please sign in to comment.