Бөлісу құралы:


Панель навигации

Элемент управления NavigationView обеспечивает навигацию верхнего уровня для вашего приложения. Он адаптируется к различным размерам экрана и поддерживает оба типа панели навигации: верхнюю и левую.

Верхняя область навигации Навигация слева
NavigationView поддерживает как верхнюю, так и левую панель или меню навигации

Это правильный контроль?

NavigationView — это адаптивный элемент управления навигацией, который обеспечивает следующее:

  • Обеспечение согласованного навигационного опыта на протяжении всего вашего приложения.
  • сохранение свободного экранного пространства в окнах меньших размеров;
  • Организация доступа ко многим категориям навигации.

Сведения о других шаблонах навигации см. в статье Основы проектирования навигации для приложений UWP.

Создание представления навигации

Значок коллекции WinUI 3 Приложение WinUI 3 Gallery содержит интерактивные примеры элементов управления и функций WinUI. Получите приложение из Microsoft Store или просмотрите исходный код GitHub.

В этом примере показано, как создать простое представление навигации в XAML.

<NavigationView>
    <NavigationView.MenuItems>
        <NavigationViewItem Content="Nav Item A"/>
        <NavigationViewItem Content="Nav Item B"/>
        <NavigationViewItem Content="Nav Item C"/>
    </NavigationView.MenuItems>

    <Frame x:Name="ContentFrame"/>
</NavigationView>

Режимы отображения

Вы можете использовать свойство PaneDisplayMode для настройки различных видов панели навигации или режимов отображения для элемента NavigationView.

Верх

Область находится над содержимым.
PaneDisplayMode="Top"

Пример верхней панели навигации

Рекомендуем использовать верхнюю панель навигации в следующих случаях:

  • у вас есть 5 или меньше категорий навигации верхнего уровня одинаковой степени важности, а дополнительные категории навигации верхнего уровня, попадающие в раскрывающееся меню переполнения, менее важны;
  • вы хотите отобразить все параметры навигации на экране;
  • для содержимого вашего приложения требуется больше пространства;
  • значки не четко описывают категории навигации приложения.

Лево

Область разворачивается и размещается слева от содержимого.
PaneDisplayMode="Left"

Пример развернутой левой панели навигации

Рекомендуем использовать левую панель навигации в следующих случаях:

  • у вас есть 5–10 одинаково важных категорий навигации верхнего уровня;
  • Вы хотите, чтобы категории навигации были очень заметными, а для другого содержимого приложения было выделено меньше места.

ЛевыйКомпактный

Панель показывает только значки до открытия и расположена слева от содержимого. При открытии панель накладывает содержимое.
PaneDisplayMode="LeftCompact"

Пример левой панели навигации в компактном режиме отображения

ЛевыйМинимальный

Пока панель закрыта, отображается лишь кнопка меню. Когда открыта, панель накладывается на левую часть содержимого.
PaneDisplayMode="LeftMinimal"

Пример левой панели навигации в минимальном режиме отображения

Auto

По умолчанию для PaneDisplayMode задано значение Auto. В режиме Auto NavigationView адаптируется между LeftMinimal, когда окно узкое, LeftCompact, а затем Left по мере расширения окна. Дополнительные сведения см. в разделе об адаптивном поведении.

Адаптивное поведение левой навигации по умолчанию
Адаптивное поведение NavigationView по умолчанию

Анатомия

На приведенных ниже изображениях представлена структура панели, верхнего колонтитула и области содержимого элемента управления при настройке верхней или левой панели навигации.

Верхний макет элемента NavigationView
Структура верхней панели навигации

Макет элемента NavigationView слева
Структура левой панели навигации

панель

Вы можете использовать свойство PaneDisplayMode для размещения панели над содержимым или слева от содержимого.

Панель NavigationView может содержать следующие объекты:

  • Объекты NavigationViewItem. Элементы навигации для перехода на определенные страницы.
  • Объекты NavigationViewItemSeparator. Разделители для группировки элементов навигации. Установите для свойства Opacity значение 0, чтобы разделитель отображался как пробел.
  • Объекты NavigationViewItemHeader. Заголовки для маркировки групп элементов.
  • Необязательный элемент управления AutoSuggestBox для поиска на уровне приложения. Назначьте элемент управления свойству NavigationView.AutoSuggestBox.
  • Необязательная точка входа для параметров приложения. Чтобы скрыть элемент параметров, задайте для свойства IsSettingsVisible значение false.

Левая панель также содержит следующие объекты:

  • Кнопка меню для переключения открытой и закрытой панели. Когда панель открыта в более крупных окнах приложения, вы можете скрыть эту кнопку с помощью свойства IsPaneToggleButtonVisible.

В NavigationView есть кнопка возврата, расположенная в верхнем левом углу области. Но она автоматически не обрабатывает переход в обратном направлении и не добавляет содержимое в обратный стек. Сведения о включении навигации в обратном направлении см. в этом разделе.

Ниже приведена подробная структура положения панелей сверху и слева.

Верхняя панель навигации

Анатомия верхней панели NavigationView

  1. Headers
  2. Элементы навигации
  3. Разделители
  4. AutoSuggestBox (необязательно)
  5. Кнопка "Параметры" (необязательно)

Левая панель навигации

Архитектура левой панели NavigationView

  1. Кнопка меню
  2. Элементы навигации
  3. Разделители
  4. Headers
  5. AutoSuggestBox (необязательно)
  6. Кнопка "Параметры" (необязательно)

С помощью FooterMenuItems вы можете поместить элементы навигации в конец панели навигации, в отличие от свойства MenuItems, которое размещает эти элементы в начале панели.

FooterMenuItems будет отображаться перед элементом Settings по умолчанию. Элемент Settings по-прежнему можно переключать с помощью свойства IsSettingsVisible.

Внутри FooterMenuItems следует размещать только элементы Навигации, а любое другое содержимое, которое должно находиться в футере панели, размещайте в элементе PaneFooter.

Пример добавления FooterMenuItems в NavigationView можно изучить в документации по классу FooterMenuItems.

На рисунке ниже показан NavigationView с учетной записью, вашей корзиной и помощью в меню в нижней части.

Элемент NavigationView с FooterMenuItems

В нижнем колонтитуле панели можно разместить содержимое в произвольном формате, добавив его в свойство PaneFooter.

Нижний колонтитул верхней панели навигации
Нижний колонтитул верхней панели

Нижний колонтитул левой панели навигации
Нижний колонтитул левой панели

Заголовок и верхний колонтитул панели

В области верхнего колонтитула панели можно разместить текстовое содержимое, задав свойство PaneTitle. Это свойство принимает строковое значение и отображает текст рядом с кнопкой меню.

Чтобы добавить нетекстовое содержимое, например изображение или логотип, вы можете поместить любой элемент в верхний колонтитул панели, добавив его в свойство PaneHeader.

Если заданы оба свойства (PaneTitle и PaneHeader), содержимое будет расположено горизонтально рядом с кнопкой меню. При этом PaneTitle будет располагаться ближе к кнопке меню.

Заголовок панели верхняя навигация
Заголовок верхней панели

Заголовок панели, левая навигация
Верхний колонтитул левой панели навигации

Содержимое панели

На панели можно разместить содержимое в произвольном формате, добавив его в свойство PaneCustomContent.

Настраиваемое содержимое, верхняя панель навигации
Настраиваемое содержимое верхней панели навигации

Панель пользовательского содержимого, левая навигация
Настраиваемое содержимое левой панели навигации

Вы можете добавить заголовок страницы, задав свойство Header.

Пример области заголовка NavigationView
Заголовок NavigationView

Область заголовка выровнена по вертикали относительно кнопки навигации в левой панели и находится под верхней панелью. Она имеет фиксированную высоту в 52 пикселя. Его назначение — хранение названия страницы для выбранной категории навигации. Заголовок закреплен в верхней части страницы и служит границей прокрутки для области содержимого.

Заголовок отображается в любой момент, когда NavigationView находится в Minimal режиме отображения. Вы можете скрыть заголовок в других режимах, используемых для окон большей ширины. Чтобы скрыть заголовок, задайте для свойства AlwaysShowHeader значение false.

Content

Пример области содержимого NavigationView
Содержимое NavigationView

Область содержимого содержит большую часть сведений для выбранной категории навигации.

Мы рекомендуем устанавливать отступы 12 px для области содержимого, если NavigationView находится в режиме Minimal, и 24 px в противном случае.

Адаптивное поведение

По умолчанию NavigationView автоматически изменяет режим отображения в зависимости от доступного экранного пространства. Свойства CompactModeThresholdWidth и ExpandedModeThresholdWidth определяют точки останова, в которых изменяется режим отображения. Вы можете изменить эти значения, чтобы настроить поведение адаптивного режима отображения.

По умолчанию

Если Параметр PaneDisplayMode имеет значение Autoпо умолчанию, адаптивное поведение — показать следующее:

  • Развернутая левая панель для окон большой ширины (1008 пикселей или больше).
  • Левая панель навигации (только для значков) на средней ширине окна (LeftCompact641 пикселей до 1007 пикселей).
  • Только кнопка меню (LeftMinimal) на небольшой ширине окна (640 пикселей или меньше).

Дополнительные сведения о размерах окон для адаптивного поведения см. в статье Размеры экрана и точки останова.

Адаптивное поведение левой навигации по умолчанию
Адаптивное поведение NavigationView по умолчанию

Минимальный

Вторым распространенным адаптивным шаблоном является использование развернутой левой панели для окон большой ширины и кнопок меню — для окон средней и небольшой ширины.

Рекомендуем использовать его в следующих случаях:

  • вам требуется больше пространства для содержимого приложения в окне небольшой ширины;
  • ваши категории навигации невозможно представить значками.

Адаптивное поведение левой панели навигации в минимальном режиме отображения
Адаптивное поведение NavigationView в минимальном режиме отображения

Чтобы настроить это поведение, задайте для свойства CompactModeThresholdWidth ширину, при которой панель будет сворачиваться. В этом примере значение по умолчанию (640) изменено на 1007. Кроме того, задайте соответствующее значение для ExpandedModeThresholdWidth, чтобы избежать конфликта значений.

<NavigationView CompactModeThresholdWidth="1007" ExpandedModeThresholdWidth="1007"/>

Компактный

Третьим распространенным адаптивным шаблоном является использование развернутой левой панели для окон большой ширины и панели навигации LeftCompact, содержащей лишь значки, — для окон средней и небольшой ширины.

Рекомендуем использовать его в следующих случаях:

  • вам необходимо, чтобы на экране всегда отображались все возможности навигации;
  • ваши категории навигации можно представить значками.

Компактное адаптивное поведение левой панели навигации
Адаптивное поведение NavigationView в компактном режиме

Чтобы настроить это поведение, задайте для свойства CompactModeThresholdWidth значение 0.

<NavigationView CompactModeThresholdWidth="0"/>

Без адаптивного поведения

Чтобы отключить автоматическое адаптивное поведение, задайте для PaneDisplayMode значение, отличное от Auto. В этом случае установлено значение LeftMinimal, поэтому отображается только кнопка меню независимо от ширины окна.

Левая панель навигации без адаптивного поведения
NavigationView с параметром PaneDisplayMode: LeftMinimal

<NavigationView PaneDisplayMode="LeftMinimal" />

Как указано выше в разделе Режимы отображения, вы можете настроить панель таким образом, чтобы она всегда отображалась сверху или же в развернутом, компактном или минимальном режимах. Вы также можете самостоятельно управлять режимами отображения в коде своего приложения. Соответствующий фрагмент кода приведен в следующем разделе.

Переключение верхней панели навигации на левую

При использовании верхней панели навигации в приложении элементы навигации свертываются в меню переполнения по мере уменьшения ширины окна. Когда окно вашего приложения узкое, улучшить пользовательский интерфейс можно, переключив PaneDisplayMode с Top на LeftMinimal навигацию, вместо того чтобы позволять всем элементам сворачиваться в меню переполнения.

Рекомендуем использовать верхнюю панель навигации для окон большого размера, а левую панель навигации для окон небольших размеров в следующих случаях:

  • у вас есть набор одинаково важных категорий навигации верхнего уровня, которые должны отображаться вместе, поэтому если одна категория в этом наборе не помещается на экране, вы переключаетесь на левую панель навигации, чтобы придать им одинаковую степень важности;
  • вам необходимо сохранить как можно больше содержимого в окнах небольшого размера.

В этом примере показано, как использовать свойство VisualStateManager и AdaptiveTrigger.MinWindowWidth для переключения между Top и LeftMinimal навигацией.

Пример 1. Адаптивное поведение верхней или левой панели навигации

<Grid>
    <NavigationView x:Name="NavigationViewControl" >
        <NavigationView.MenuItems>
            <NavigationViewItem Content="A" x:Name="A" />
            <NavigationViewItem Content="B" x:Name="B" />
            <NavigationViewItem Content="C" x:Name="C" />
        </NavigationView.MenuItems>
    </NavigationView>

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState>
                <VisualState.StateTriggers>
                    <AdaptiveTrigger
                        MinWindowWidth="{x:Bind NavigationViewControl.CompactModeThresholdWidth}" />
                </VisualState.StateTriggers>

                <VisualState.Setters>
                    <Setter Target="NavigationViewControl.PaneDisplayMode" Value="Top"/>
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Grid>

Подсказка

При использовании AdaptiveTrigger.MinWindowWidth визуальное состояние активируется, если ширина окна превышает указанное минимальное значение. Это означает, что XAML по умолчанию определяет узкое окно, а VisualState определяет модификации, которые применяются при увеличении ширины окна. Значение по умолчанию PaneDisplayMode для NavigationView — auto, поэтому если ширина окна меньше или равна CompactModeThresholdWidth, LeftMinimal используется навигация. Когда окно становится шире, VisualState переопределяет значение по умолчанию и Top используется навигация.

NavigationView не выполняет никаких задач навигации автоматически. При касании элемента навигации NavigationView выделяет этот элемент как выбранный и вызывает событие ItemInvoked. Если в результате касания выбран новый элемент, также возникает событие SelectionChanged.

Вы можете обрабатывать любое событие для выполнения задач, связанных с запрошенной навигацией. Какое из событий необходимо обработать зависит от поведения вашего приложения. Обычно в ответ на эти события вы переходите на запрошенную страницу, а заголовок NavigationView изменяется.

  • Событие ItemInvoked вызывается каждый раз, когда пользователь касается элемента навигации, даже если он уже выбран. (Элемент также можно вызвать с эквивалентным действием с помощью мыши, клавиатуры или других входных данных. Дополнительные сведения см. в разделе "Входные данные и взаимодействие".) При переходе по обработчику ItemInvoked по умолчанию страница будет перезагружена, а в стек навигации добавляется повторяющаяся запись. Если вы выполняете переход при вызове элемента, запретите перезагрузку страницы или убедитесь, что при повторной загрузке страницы в обратном стеке навигации не создается повторяющаяся запись. (См. примеры кода.)
  • Событие SelectionChanged может быть инициировано пользователем, который вызывает не выбранный в настоящее время элемент или изменяет выбранный элемент программным способом. Если изменение выбора происходит из-за вызова элемента, сначала возникает событие ItemInvoked. Если изменение выбора выполняется программным способом, событие ItemInvoked не вызывается.

Все элементы навигации являются частью MenuItems или FooterMenuItems, и все они принадлежат к одной модели выбора. В каждый момент времени может быть выбран только один элемент навигации.

Обратная навигация

NavigationView содержит встроенную кнопку возврата. Но, как и переход вперед, навигация в обратном направлении не выполняется автоматически. При нажатии кнопки возврата возникает событие BackRequested. Вы обрабатываете это событие для выполнения перехода в обратном направлении. Дополнительные сведения и примеры кода см. в статье Журнал навигации и навигация в обратном направлении.

В Minimal или Compact в режиме навигационный объект Pane открывается как всплывающий элемент. В этом случае нажатие кнопки "Назад" закроет Pane и вызовет событие PaneClosing.

Вы можете скрыть или отключить кнопку возврата, задав следующие свойства:

  • IsBackButtonVisible — используйте, чтобы отобразить и скрыть кнопку возврата. Это свойство принимает значение перечисления NavigationViewBackButtonVisible и имеет значение Auto по умолчанию. Если кнопка свернута, в макете для нее не зарезервировано пространство.
  • IsBackEnabled — используйте, чтобы включить или отключить кнопку возврата. Вы можете привязать данные этого свойства к свойству CanGoBack вашего фрейма навигации. BackRequested не вызывается, если IsBackEnabled есть false.

Кнопка
Кнопка "Назад" в левой области навигации

Кнопка
Кнопка "Назад" в верхней области навигации

Пример кода

В этом примере показано, как можно использовать NavigationView с верхней областью навигации для окон больших размеров и левой областью навигации для окон небольших размеров. Его можно адаптировать только для левой панели навигации, удалив параметры верхней панели навигации в VisualStateManager.

В этом примере показан общий способ настройки данных навигации, которые будут работать для многих сценариев. В этом примере вы сначала сохраните (в теге NavigationViewItem) полное имя страницы, на которую вы хотите перейти. В обработчике событий вы распаковываете это значение, преобразуете его в объект Type(C#) или Windows::UI::Xaml::Interop::TypeName(C++/WinRT) и используйте его для перехода на целевую страницу. Это позволяет создавать модульные тесты, чтобы убедиться, что значения внутри тегов имеют допустимый тип. (См. также Упаковка и распаковка значений в IInspectable с использованием C++/WinRT). В нем также показано, как реализовать обратную навигацию с помощью кнопки "Назад" NavigationView.

Этот код предполагает, что приложение содержит страницы со следующими именами, чтобы перейти к: HomePage, AppsPage, GamesPage, MusicPage, MyContentPage и SettingsPage. Код для этих страниц не отображается.

<Page ... >
 <Grid>
     <NavigationView x:Name="NavView"
                     Loaded="NavView_Loaded"
                     ItemInvoked="NavView_ItemInvoked"
                     BackRequested="NavView_BackRequested">
         <NavigationView.MenuItems>
             <NavigationViewItem Tag="NavigationViewDemo.HomePage" Icon="Home" Content="Home"/>
             <NavigationViewItemSeparator/>
             <NavigationViewItemHeader x:Name="MainPagesHeader"
                                       Content="Main pages"/>
             <NavigationViewItem Tag="NavigationViewDemo.AppsPage" Content="Apps">
                 <NavigationViewItem.Icon>
                     <FontIcon Glyph="&#xEB3C;"/>
                 </NavigationViewItem.Icon>
             </NavigationViewItem>
             <NavigationViewItem Tag="NavigationViewDemo.GamesPage" Content="Games">
                 <NavigationViewItem.Icon>
                     <FontIcon Glyph="&#xE7FC;"/>
                 </NavigationViewItem.Icon>
             </NavigationViewItem>
             <NavigationViewItem Tag="NavigationViewDemo.MusicPage" Icon="Audio" Content="Music"/>
         </NavigationView.MenuItems>

         <NavigationView.AutoSuggestBox>
             <!-- See AutoSuggestBox documentation for
              more info about how to implement search. -->
             <AutoSuggestBox x:Name="NavViewSearchBox" QueryIcon="Find"/>
         </NavigationView.AutoSuggestBox>

         <ScrollViewer>
             <Frame x:Name="ContentFrame" IsTabStop="True"
                NavigationFailed="ContentFrame_NavigationFailed"/>
         </ScrollViewer>
     </NavigationView>

     <VisualStateManager.VisualStateGroups>
         <VisualStateGroup>
             <VisualState>
                 <VisualState.StateTriggers>
                     <AdaptiveTrigger
                     MinWindowWidth="{x:Bind NavViewCompactModeThresholdWidth}"/>
                 </VisualState.StateTriggers>
                 <VisualState.Setters>
                     <!-- Remove the next 3 lines for left-only navigation. -->
                     <Setter Target="NavView.PaneDisplayMode" Value="Top"/>
                     <Setter Target="NavViewSearchBox.Width" Value="200"/>
                     <Setter Target="MainPagesHeader.Visibility" Value="Collapsed"/>
                     <!-- Leave the next line for left-only navigation. -->
                     <Setter Target="ContentFrame.Padding" Value="24,0,24,24"/>
                 </VisualState.Setters>
             </VisualState>
         </VisualStateGroup>
     </VisualStateManager.VisualStateGroups>
 </Grid>
</Page>
private double NavViewCompactModeThresholdWidth { get { return NavView.CompactModeThresholdWidth; } }

private void ContentFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
    throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}

private void NavView_Loaded(object sender, RoutedEventArgs e)
{
    // You can also add items in code.
    NavView.MenuItems.Add(new NavigationViewItemSeparator());
    NavView.MenuItems.Add(new NavigationViewItem
    {
        Content = "My content",
        Icon = new SymbolIcon((Symbol)0xF1AD),
        Tag = "NavigationViewDemo.MyContentPage"
    });

    // Add handler for ContentFrame navigation.
    ContentFrame.Navigated += On_Navigated;

    // NavView doesn't load any page by default, so load home page.
    NavView.SelectedItem = NavView.MenuItems[0];
    // If navigation occurs on SelectionChanged, this isn't needed.
    // Because we use ItemInvoked to navigate, we need to call Navigate
    // here to load the home page.
    NavView_Navigate(typeof(HomePage), new EntranceNavigationTransitionInfo());
}

private void NavView_ItemInvoked(NavigationView sender,
                                 NavigationViewItemInvokedEventArgs args)
{
    if (args.IsSettingsInvoked == true)
    {
        NavView_Navigate(typeof(SettingsPage), args.RecommendedNavigationTransitionInfo);
    }
    else if (args.InvokedItemContainer != null)
    {
        Type navPageType = Type.GetType(args.InvokedItemContainer.Tag.ToString());
        NavView_Navigate(navPageType, args.RecommendedNavigationTransitionInfo);
    }
}

// NavView_SelectionChanged is not used in this example, but is shown for completeness.
// You will typically handle either ItemInvoked or SelectionChanged to perform navigation,
// but not both.
private void NavView_SelectionChanged(NavigationView sender,
                                      NavigationViewSelectionChangedEventArgs args)
{
    if (args.IsSettingsSelected == true)
    {
        NavView_Navigate(typeof(SettingsPage), args.RecommendedNavigationTransitionInfo);
    }
    else if (args.SelectedItemContainer != null)
    {
        Type navPageType = Type.GetType(args.SelectedItemContainer.Tag.ToString());
        NavView_Navigate(navPageType, args.RecommendedNavigationTransitionInfo);
    }
}

private void NavView_Navigate(
    Type navPageType,
    NavigationTransitionInfo transitionInfo)
{
    // Get the page type before navigation so you can prevent duplicate
    // entries in the backstack.
    Type preNavPageType = ContentFrame.CurrentSourcePageType;

    // Only navigate if the selected page isn't currently loaded.
    if (navPageType is not null && !Type.Equals(preNavPageType, navPageType))
    {
        ContentFrame.Navigate(navPageType, null, transitionInfo);
    }
}

private void NavView_BackRequested(NavigationView sender,
                                   NavigationViewBackRequestedEventArgs args)
{
    TryGoBack();
}

private bool TryGoBack()
{
    if (!ContentFrame.CanGoBack)
        return false;

    // Don't go back if the nav pane is overlayed.
    if (NavView.IsPaneOpen &&
        (NavView.DisplayMode == NavigationViewDisplayMode.Compact ||
         NavView.DisplayMode == NavigationViewDisplayMode.Minimal))
        return false;

    ContentFrame.GoBack();
    return true;
}

private void On_Navigated(object sender, NavigationEventArgs e)
{
    NavView.IsBackEnabled = ContentFrame.CanGoBack;

    if (ContentFrame.SourcePageType == typeof(SettingsPage))
    {
        // SettingsItem is not part of NavView.MenuItems, and doesn't have a Tag.
        NavView.SelectedItem = (NavigationViewItem)NavView.SettingsItem;
        NavView.Header = "Settings";
    }
    else if (ContentFrame.SourcePageType != null)
    {
        // Select the nav view item that corresponds to the page being navigated to.
        NavView.SelectedItem = NavView.MenuItems
                    .OfType<NavigationViewItem>()
                    .First(i => i.Tag.Equals(ContentFrame.SourcePageType.FullName.ToString()));

        NavView.Header =
            ((NavigationViewItem)NavView.SelectedItem)?.Content?.ToString();

    }
}
// MainPage.idl
runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
{
    ...
    Double NavViewCompactModeThresholdWidth{ get; };
}

// pch.h
...
#include <winrt/Windows.UI.Xaml.Interop.h>
#include <winrt/Microsoft.UI.Xaml.Media.Animation.h>


// MainPage.h
#pragma once

#include "MainPage.g.h"

namespace muxc
{
    using namespace winrt::Microsoft::UI::Xaml::Controls;
};

namespace winrt::NavigationViewDemo::implementation
{
    struct MainPage : MainPageT<MainPage>
    {
        MainPage();

        double NavViewCompactModeThresholdWidth();
        void ContentFrame_NavigationFailed(
            Windows::Foundation::IInspectable const& /* sender */,
            Microsoft::UI::Xaml::Navigation::NavigationFailedEventArgs const& args);
        void NavView_Loaded(
            Windows::Foundation::IInspectable const& /* sender */,
            Microsoft::UI::Xaml::RoutedEventArgs const& /* args */);
        void NavView_ItemInvoked(
            Windows::Foundation::IInspectable const& /* sender */,
            muxc::NavigationViewItemInvokedEventArgs const& args);

        // NavView_SelectionChanged is not used in this example, but is shown for completeness.
        // You'll typically handle either ItemInvoked or SelectionChanged to perform navigation,
        // but not both.
        void NavView_SelectionChanged(
            muxc::NavigationView const& /* sender */,
            muxc::NavigationViewSelectionChangedEventArgs const& args);
        void NavView_Navigate(
            Windows::UI::Xaml::Interop::TypeName navPageType,
            Microsoft::UI::Xaml::Media::Animation::NavigationTransitionInfo const& transitionInfo);
        void NavView_BackRequested(
            muxc::NavigationView const& /* sender */,
            muxc::NavigationViewBackRequestedEventArgs const& /* args */);
        void On_Navigated(
            Windows::Foundation::IInspectable const& /* sender */,
            Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& args);
        bool TryGoBack();

    private:

    };
}

namespace winrt::NavigationViewDemo::factory_implementation
{
    struct MainPage : MainPageT<MainPage, implementation::MainPage>
    {
    };
}

// MainPage.cpp
#include "pch.h"
#include "MainPage.xaml.h"
#if __has_include("MainPage.g.cpp")
#include "MainPage.g.cpp"
#endif

using namespace winrt;
using namespace Microsoft::UI::Xaml;

namespace winrt::NavigationViewDemo::implementation
{
    MainPage::MainPage()
    {
        InitializeComponent();
    }

    double MainPage::NavViewCompactModeThresholdWidth()
    {
        return NavView().CompactModeThresholdWidth();
    }

    void MainPage::ContentFrame_NavigationFailed(
        Windows::Foundation::IInspectable const& /* sender */,
        Microsoft::UI::Xaml::Navigation::NavigationFailedEventArgs const& args)
    {
        throw winrt::hresult_error(
            E_FAIL, winrt::hstring(L"Failed to load Page ") + args.SourcePageType().Name);
    }

    void MainPage::NavView_Loaded(
        Windows::Foundation::IInspectable const& /* sender */,
        Microsoft::UI::Xaml::RoutedEventArgs const& /* args */)
    {
        // You can also add items in code.
        NavView().MenuItems().Append(muxc::NavigationViewItemSeparator());
        muxc::NavigationViewItem navigationViewItem;
        navigationViewItem.Content(winrt::box_value(L"My content"));
        navigationViewItem.Icon(muxc::SymbolIcon(static_cast<muxc::Symbol>(0xF1AD)));
        navigationViewItem.Tag(winrt::box_value(L"NavigationViewDemo.MyContentPage"));
        NavView().MenuItems().Append(navigationViewItem);

        // Add handler for ContentFrame navigation.
        ContentFrame().Navigated({ this, &MainPage::On_Navigated });

        // NavView doesn't load any page by default, so load home page.
        NavView().SelectedItem(NavView().MenuItems().GetAt(0));
        // If navigation occurs on SelectionChanged, then this isn't needed.
        // Because we use ItemInvoked to navigate, we need to call Navigate
        // here to load the home page.
        NavView_Navigate(winrt::xaml_typename<NavigationViewDemo::HomePage>(),
            Microsoft::UI::Xaml::Media::Animation::EntranceNavigationTransitionInfo());
    }

    void MainPage::NavView_ItemInvoked(
        Windows::Foundation::IInspectable const& /* sender */,
        muxc::NavigationViewItemInvokedEventArgs const& args)
    {
        if (args.IsSettingsInvoked())
        {
            NavView_Navigate(winrt::xaml_typename<NavigationViewDemo::SettingsPage>(),
                args.RecommendedNavigationTransitionInfo());
        }
        else if (args.InvokedItemContainer())
        {
            Windows::UI::Xaml::Interop::TypeName pageTypeName;
            pageTypeName.Name = unbox_value<hstring>(args.InvokedItemContainer().Tag());
            pageTypeName.Kind = Windows::UI::Xaml::Interop::TypeKind::Primitive;
            NavView_Navigate(pageTypeName, args.RecommendedNavigationTransitionInfo());
        }
    }

    // NavView_SelectionChanged is not used in this example, but is shown for completeness.
    // You will typically handle either ItemInvoked or SelectionChanged to perform navigation,
    // but not both.
    void MainPage::NavView_SelectionChanged(
        muxc::NavigationView const& /* sender */,
        muxc::NavigationViewSelectionChangedEventArgs const& args)
    {
        if (args.IsSettingsSelected())
        {
            NavView_Navigate(winrt::xaml_typename<NavigationViewDemo::SettingsPage>(),
                args.RecommendedNavigationTransitionInfo());
        }
        else if (args.SelectedItemContainer())
        {
            Windows::UI::Xaml::Interop::TypeName pageTypeName;
            pageTypeName.Name = unbox_value<hstring>(args.SelectedItemContainer().Tag());
            pageTypeName.Kind = Windows::UI::Xaml::Interop::TypeKind::Primitive;
            NavView_Navigate(pageTypeName, args.RecommendedNavigationTransitionInfo());
        }
    }

    void MainPage::NavView_Navigate(
        Windows::UI::Xaml::Interop::TypeName navPageType,
        Microsoft::UI::Xaml::Media::Animation::NavigationTransitionInfo const& transitionInfo)
    {
        // Get the page type before navigation so you can prevent duplicate
        // entries in the backstack.
        Windows::UI::Xaml::Interop::TypeName preNavPageType =
            ContentFrame().CurrentSourcePageType();

        // Navigate only if the selected page isn't currently loaded.
        if (navPageType.Name != L"" && preNavPageType.Name != navPageType.Name)
        {
            ContentFrame().Navigate(navPageType, nullptr, transitionInfo);
        }
    }

    void MainPage::NavView_BackRequested(
        muxc::NavigationView const& /* sender */,
        muxc::NavigationViewBackRequestedEventArgs const& /* args */)
    {
        TryGoBack();
    }

    bool MainPage::TryGoBack()
    {
        if (!ContentFrame().CanGoBack())
            return false;
        // Don't go back if the nav pane is overlayed.
        if (NavView().IsPaneOpen() &&
            (NavView().DisplayMode() == muxc::NavigationViewDisplayMode::Compact ||
                NavView().DisplayMode() == muxc::NavigationViewDisplayMode::Minimal))
            return false;
        ContentFrame().GoBack();
        return true;
    }

    void MainPage::On_Navigated(
        Windows::Foundation::IInspectable const& /* sender */,
        Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& args)
    {
        NavView().IsBackEnabled(ContentFrame().CanGoBack());

        if (ContentFrame().SourcePageType().Name ==
            winrt::xaml_typename<NavigationViewDemo::SettingsPage>().Name)
        {
            // SettingsItem is not part of NavView.MenuItems, and doesn't have a Tag.
            NavView().SelectedItem(NavView().SettingsItem().as<muxc::NavigationViewItem>());
            NavView().Header(winrt::box_value(L"Settings"));
        }
        else if (ContentFrame().SourcePageType().Name != L"")
        {
            for (auto&& eachMenuItem : NavView().MenuItems())
            {
                auto navigationViewItem =
                    eachMenuItem.try_as<muxc::NavigationViewItem>();
                {
                    if (navigationViewItem)
                    {
                        winrt::hstring hstringValue =
                            winrt::unbox_value_or<winrt::hstring>(
                                navigationViewItem.Tag(), L"");
                        if (hstringValue == ContentFrame().SourcePageType().Name)
                        {
                            NavView().SelectedItem(navigationViewItem);
                            NavView().Header(navigationViewItem.Content());
                        }
                    }
                }
            }
        }
    }
}

Иерархическая навигация

Некоторые приложения могут иметь более сложную иерархическую структуру, которая требует больше, чем просто неструктурированный список элементов навигации. Для отображения категорий страниц можно использовать элементы навигации верхнего уровня, при этом дочерние элементы отображают определенные страницы. Это также полезно при наличии сводных страниц, которые связаны только с другими страницами. В подобных случаях следует создать иерархическое представление NavigationView.

Чтобы отобразить иерархический список вложенных элементов навигации на панели, используйте свойство MenuItems или свойство MenuItemsSource объекта NavigationViewItem. Каждый объект NavigationViewItem может содержать другие объекты NavigationViewItem и организационные элементы, такие как заголовки элементов и разделители. Чтобы отобразить иерархический список при использовании MenuItemsSource, назначьте ItemTemplate объектом NavigationViewItem и привяжите его свойство MenuItemsSource к следующему уровню иерархии.

Хотя объект NavigationViewItem может содержать любое количество вложенных уровней, рекомендуется не делать иерархию навигации вашего приложения слишком глубокой. Мы считаем, что двухуровневая навигация идеально сочетает удобство использования и понятность.

NavigationView отображает иерархию в Top, Left, и LeftCompact режимах отображения области. Вот как выглядит развернутое поддерево в каждом из режимов отображения панели:

NavigationView с иерархией

Добавление иерархии элементов в разметку

В этом примере показано, как объявить иерархическую навигацию приложения в разметке XAML.

<NavigationView>
    <NavigationView.MenuItems>
        <NavigationViewItem Content="Home" Icon="Home" ToolTipService.ToolTip="Home"/>
        <NavigationViewItem Content="Collections" Icon="Keyboard" ToolTipService.ToolTip="Collections">
            <NavigationViewItem.MenuItems>
                <NavigationViewItem Content="Notes" Icon="Page" ToolTipService.ToolTip="Notes"/>
                <NavigationViewItem Content="Mail" Icon="Mail" ToolTipService.ToolTip="Mail"/>
            </NavigationViewItem.MenuItems>
        </NavigationViewItem>
    </NavigationView.MenuItems>
</NavigationView>

Добавление иерархии элементов с помощью привязки данных

Добавьте иерархию пунктов меню в элемент NavigationView с помощью

  • привязки свойства MenuItemsSource к иерархическим данным
  • определения шаблона элемента как объекта NavigationViewMenuItem, когда его содержимое должно быть меткой элемента меню, а свойство MenuItemsSource привязано к следующему уровню иерархии

В этом примере также демонстрируются события Развертывание и Свертывание. Эти события вызываются для пункта меню с дочерними элементами.

<Page ... >
    <Page.Resources>
        <DataTemplate x:Key="NavigationViewMenuItem" x:DataType="local:Category">
            <NavigationViewItem Content="{x:Bind Name}" MenuItemsSource="{x:Bind Children}"/>
        </DataTemplate>
    </Page.Resources>

    <Grid>
        <NavigationView x:Name="navview"
    MenuItemsSource="{x:Bind Categories, Mode=OneWay}"
    MenuItemTemplate="{StaticResource NavigationViewMenuItem}"
    ItemInvoked="{x:Bind OnItemInvoked}"
    Expanding="OnItemExpanding"
    Collapsed="OnItemCollapsed"
    PaneDisplayMode="Left">
            <StackPanel Margin="10,10,0,0">
                <TextBlock Margin="0,10,0,0" x:Name="ExpandingItemLabel" Text="Last Expanding: N/A"/>
                <TextBlock x:Name="CollapsedItemLabel" Text="Last Collapsed: N/A"/>
            </StackPanel>
        </NavigationView>
    </Grid>
</Page>
public class Category
{
    public String Name { get; set; }
    public String CategoryIcon { get; set; }
    public ObservableCollection<Category> Children { get; set; }
}

public sealed partial class HierarchicalNavigationViewDataBinding : Page
{
    public HierarchicalNavigationViewDataBinding()
    {
        this.InitializeComponent();
    }

    public ObservableCollection<Category> Categories = new ObservableCollection<Category>()
    {
        new Category(){
            Name = "Menu item 1",
            CategoryIcon = "Icon",
            Children = new ObservableCollection<Category>() {
                new Category(){
                    Name = "Menu item 2",
                    CategoryIcon = "Icon",
                    Children = new ObservableCollection<Category>() {
                        new Category() {
                            Name  = "Menu item 3",
                            CategoryIcon = "Icon",
                            Children = new ObservableCollection<Category>() {
                                new Category() { Name  = "Menu item 4", CategoryIcon = "Icon" },
                                new Category() { Name  = "Menu item 5", CategoryIcon = "Icon" }
                            }
                        }
                    }
                }
            }
        },
        new Category(){
            Name = "Menu item 6",
            CategoryIcon = "Icon",
            Children = new ObservableCollection<Category>() {
                new Category(){
                    Name = "Menu item 7",
                    CategoryIcon = "Icon",
                    Children = new ObservableCollection<Category>() {
                        new Category() { Name  = "Menu item 8", CategoryIcon = "Icon" },
                        new Category() { Name  = "Menu item 9", CategoryIcon = "Icon" }
                    }
                }
            }
        },
        new Category(){ Name = "Menu item 10", CategoryIcon = "Icon" }
    };

    private void OnItemInvoked(object sender, NavigationViewItemInvokedEventArgs e)
    {
        var clickedItem = e.InvokedItem;
        var clickedItemContainer = e.InvokedItemContainer;
    }
    private void OnItemExpanding(object sender, NavigationViewItemExpandingEventArgs e)
    {
        var nvib = e.ExpandingItemContainer;
        var name = "Last expanding: " + nvib.Content.ToString();
        ExpandingItemLabel.Text = name;
    }
    private void OnItemCollapsed(object sender, NavigationViewItemCollapsedEventArgs e)
    {
        var nvib = e.CollapsedItemContainer;
        var name = "Last collapsed: " + nvib.Content;
        CollapsedItemLabel.Text = name;
    }
}
// Category.idl
namespace HierarchicalNavigationViewDataBinding
{
    runtimeclass Category
    {
        String Name;
        String CategoryIcon;
        Windows.Foundation.Collections.IObservableVector<Category> Children;
    }
}

// Category.h
#pragma once
#include "Category.g.h"

namespace winrt::HierarchicalNavigationViewDataBinding::implementation
{
    struct Category : CategoryT<Category>
    {
        Category();
        Category(winrt::hstring name,
            winrt::hstring categoryIcon,
            Windows::Foundation::Collections::
                IObservableVector<HierarchicalNavigationViewDataBinding::Category> children);

        winrt::hstring Name();
        void Name(winrt::hstring const& value);
        winrt::hstring CategoryIcon();
        void CategoryIcon(winrt::hstring const& value);
        Windows::Foundation::Collections::
            IObservableVector<HierarchicalNavigationViewDataBinding::Category> Children();
        void Children(Windows::Foundation::Collections:
            IObservableVector<HierarchicalNavigationViewDataBinding::Category> const& value);

    private:
        winrt::hstring m_name;
        winrt::hstring m_categoryIcon;
        Windows::Foundation::Collections::
            IObservableVector<HierarchicalNavigationViewDataBinding::Category> m_children;
    };
}

// Category.cpp
#include "pch.h"
#include "Category.h"
#include "Category.g.cpp"

namespace winrt::HierarchicalNavigationViewDataBinding::implementation
{
    Category::Category()
    {
        m_children = winrt::single_threaded_observable_vector<HierarchicalNavigationViewDataBinding::Category>();
    }

    Category::Category(
        winrt::hstring name,
        winrt::hstring categoryIcon,
        Windows::Foundation::Collections::
            IObservableVector<HierarchicalNavigationViewDataBinding::Category> children)
    {
        m_name = name;
        m_categoryIcon = categoryIcon;
        m_children = children;
    }

    hstring Category::Name()
    {
        return m_name;
    }

    void Category::Name(hstring const& value)
    {
        m_name = value;
    }

    hstring Category::CategoryIcon()
    {
        return m_categoryIcon;
    }

    void Category::CategoryIcon(hstring const& value)
    {
        m_categoryIcon = value;
    }

    Windows::Foundation::Collections::IObservableVector<HierarchicalNavigationViewDataBinding::Category>
        Category::Children()
    {
        return m_children;
    }

    void Category::Children(
        Windows::Foundation::Collections::IObservableVector<HierarchicalNavigationViewDataBinding::Category>
            const& value)
    {
        m_children = value;
    }
}

// MainPage.idl
import "Category.idl";

namespace HierarchicalNavigationViewDataBinding
{
    [default_interface]
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        Windows.Foundation.Collections.IObservableVector<Category> Categories{ get; };
    }
}

// MainPage.h
#pragma once

#include "MainPage.g.h"

namespace muxc
{
    using namespace winrt::Microsoft::UI::Xaml::Controls;
};

namespace winrt::HierarchicalNavigationViewDataBinding::implementation
{
    struct MainPage : MainPageT<MainPage>
    {
        MainPage();

        Windows::Foundation::Collections::IObservableVector<HierarchicalNavigationViewDataBinding::Category>
            Categories();

        void OnItemInvoked(muxc::NavigationView const& sender, muxc::NavigationViewItemInvokedEventArgs const& args);
        void OnItemExpanding(
            muxc::NavigationView const& sender,
            muxc::NavigationViewItemExpandingEventArgs const& args);
        void OnItemCollapsed(
            muxc::NavigationView const& sender,
            muxc::NavigationViewItemCollapsedEventArgs const& args);

    private:
        Windows::Foundation::Collections::
            IObservableVector<HierarchicalNavigationViewDataBinding::Category> m_categories;
    };
}

namespace winrt::HierarchicalNavigationViewDataBinding::factory_implementation
{
    struct MainPage : MainPageT<MainPage, implementation::MainPage>
    {
    };
}

// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"

#include "Category.h"

namespace winrt::HierarchicalNavigationViewDataBinding::implementation
{
    MainPage::MainPage()
    {
        InitializeComponent();

        m_categories =
            winrt::single_threaded_observable_vector<HierarchicalNavigationViewDataBinding::Category>();

        auto menuItem10 = winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
            (L"Menu item 10", L"Icon", nullptr);

        auto menuItem9 = winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
            (L"Menu item 9", L"Icon", nullptr);
        auto menuItem8 = winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
            (L"Menu item 8", L"Icon", nullptr);
        auto menuItem7Children =
            winrt::single_threaded_observable_vector<HierarchicalNavigationViewDataBinding::Category>();
        menuItem7Children.Append(*menuItem9);
        menuItem7Children.Append(*menuItem8);

        auto menuItem7 = winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
            (L"Menu item 7", L"Icon", menuItem7Children);
        auto menuItem6Children =
            winrt::single_threaded_observable_vector<HierarchicalNavigationViewDataBinding::Category>();
        menuItem6Children.Append(*menuItem7);

        auto menuItem6 = winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
            (L"Menu item 6", L"Icon", menuItem6Children);

        auto menuItem5 = winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
            (L"Menu item 5", L"Icon", nullptr);
        auto menuItem4 = winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
            (L"Menu item 4", L"Icon", nullptr);
        auto menuItem3Children =
            winrt::single_threaded_observable_vector<HierarchicalNavigationViewDataBinding::Category>();
        menuItem3Children.Append(*menuItem5);
        menuItem3Children.Append(*menuItem4);

        auto menuItem3 = winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
            (L"Menu item 3", L"Icon", menuItem3Children);
        auto menuItem2Children =
            winrt::single_threaded_observable_vector<HierarchicalNavigationViewDataBinding::Category>();
        menuItem2Children.Append(*menuItem3);

        auto menuItem2 = winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
            (L"Menu item 2", L"Icon", menuItem2Children);
        auto menuItem1Children =
            winrt::single_threaded_observable_vector<HierarchicalNavigationViewDataBinding::Category>();
        menuItem1Children.Append(*menuItem2);

        auto menuItem1 = winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
            (L"Menu item 1", L"Icon", menuItem1Children);

        m_categories.Append(*menuItem1);
        m_categories.Append(*menuItem6);
        m_categories.Append(*menuItem10);
    }

    Windows::Foundation::Collections::IObservableVector<HierarchicalNavigationViewDataBinding::Category>
        MainPage::Categories()
    {
        return m_categories;
    }

    void MainPage::OnItemInvoked(
        muxc::NavigationView const& /* sender */,
        muxc::NavigationViewItemInvokedEventArgs const& args)
    {
        auto clickedItem = args.InvokedItem();
        auto clickedItemContainer = args.InvokedItemContainer();
    }

    void MainPage::OnItemExpanding(
        muxc::NavigationView const& /* sender */,
        muxc::NavigationViewItemExpandingEventArgs const& args)
    {
        auto nvib = args.ExpandingItemContainer();
        auto name = L"Last expanding: " + winrt::unbox_value<winrt::hstring>(nvib.Content());
        ExpandingItemLabel().Text(name);
    }

    void MainPage::OnItemCollapsed(
        muxc::NavigationView const& /* sender */,
        muxc::NavigationViewItemCollapsedEventArgs const& args)
    {
        auto nvib = args.CollapsedItemContainer();
        auto name = L"Last collapsed: " + winrt::unbox_value<winrt::hstring>(nvib.Content());
        CollapsedItemLabel().Text(name);
    }
}

Отбор

По умолчанию каждый элемент может содержать дочерние элементы, вызываться или быть выбранным.

Предоставляя пользователям иерархическое дерево вариантов навигации можно сделать родительские элементы невыбираемыми, например, если у приложения нет конечной страницы, связанной с этим родительским элементом. Если родительские элементы доступны для выбора, рекомендуется использовать режимы представления Left-Expanded или Top. Использование режима LeftCompact приведет к тому, что пользователь будет переходить к родительскому элементу, чтобы открывать дочернее дерево при каждом вызове элемента.

Индикаторы выбора будут отображаться на выбранных элементах вдоль левого края в режиме левого или на нижней границе в режиме верхнего. Ниже показаны объекты NavigationView в левом и верхнем режимах, где выбран родительский элемент.

Представление NavigationView в левом режиме с выделенным родительским элементом

NavigationView в верхнем режиме с выбранным родительским элементом

Выбранный элемент может не всегда оставаться видимым. Если выбран дочерний элемент в свернутом или несвернутом поддереве, то его первый видимый предок будет показан как выбранный. Индикатор выделения вернется к выбранному элементу при развертывании поддерева.

Например, на рисунке выше пользователь может выбрать элемент Календаря, а затем может свернуть его поддерево. В этом случае индикатор выбора будет отображаться ниже элемента Учетной Записи, так как Учетная Запись является первым видимым предком Календаря. Индикатор выделения вернется к элементу Календаря, когда пользователь снова развернет поддерево.

NavigationView будет отображать не более одного индикатора выбора.

В режимах Top и Left при щелчке по стрелкам в объектах NavigationViewItem поддерево разворачивается или сворачивается. Если щелкнуть или коснуться где-то в другом месте на NavigationViewItem, будет активировано событие ItemInvoked, а также поддерево будет свернуто или развернуто.

Чтобы элемент не отображал индикатор выбора при вызове, установите для свойства SelectsOnInvoked значение false, как показано ниже:

<Page ...>
    <Page.Resources>
        <DataTemplate x:Key="NavigationViewMenuItem" x:DataType="local:Category">
            <NavigationViewItem Content="{x:Bind Name}"
            MenuItemsSource="{x:Bind Children}"
            SelectsOnInvoked="{x:Bind IsLeaf}"/>
        </DataTemplate>
    </Page.Resources>

    <Grid>
        <NavigationView x:Name="navview"
    MenuItemsSource="{x:Bind Categories, Mode=OneWay}"
    MenuItemTemplate="{StaticResource NavigationViewMenuItem}">
        </NavigationView>
    </Grid>
</Page>
public class Category
{
    public String Name { get; set; }
    public String CategoryIcon { get; set; }
    public ObservableCollection<Category> Children { get; set; }
    public bool IsLeaf { get; set; }
}

public sealed partial class HierarchicalNavigationViewDataBinding : Page
{
    public HierarchicalNavigationViewDataBinding()
    {
        this.InitializeComponent();
    }

    public ObservableCollection<Category> Categories = new ObservableCollection<Category>()
    {
        new Category(){
            Name = "Menu item 1",
            CategoryIcon = "Icon",
            Children = new ObservableCollection<Category>() {
                new Category(){
                    Name = "Menu item 2",
                    CategoryIcon = "Icon",
                    Children = new ObservableCollection<Category>() {
                        new Category() {
                            Name  = "Menu item 3",
                            CategoryIcon = "Icon",
                            Children = new ObservableCollection<Category>() {
                                new Category() { Name  = "Menu item 4", CategoryIcon = "Icon", IsLeaf = true },
                                new Category() { Name  = "Menu item 5", CategoryIcon = "Icon", IsLeaf = true }
                            }
                        }
                    }
                }
            }
        },
        new Category(){
            Name = "Menu item 6",
            CategoryIcon = "Icon",
            Children = new ObservableCollection<Category>() {
                new Category(){
                    Name = "Menu item 7",
                    CategoryIcon = "Icon",
                    Children = new ObservableCollection<Category>() {
                        new Category() { Name  = "Menu item 8", CategoryIcon = "Icon", IsLeaf = true },
                        new Category() { Name  = "Menu item 9", CategoryIcon = "Icon", IsLeaf = true }
                    }
                }
            }
        },
        new Category(){ Name = "Menu item 10", CategoryIcon = "Icon", IsLeaf = true }
    };
}
// Category.idl
namespace HierarchicalNavigationViewDataBinding
{
    runtimeclass Category
    {
        ...
        Boolean IsLeaf;
    }
}

// Category.h
...
struct Category : CategoryT<Category>
{
    ...
    Category(winrt::hstring name,
        winrt::hstring categoryIcon,
        Windows::Foundation::Collections::IObservableVector<HierarchicalNavigationViewDataBinding::Category> children,
        bool isleaf = false);
    ...
    bool IsLeaf();
    void IsLeaf(bool value);

private:
    ...
    bool m_isleaf;
};

// Category.cpp
...
Category::Category(winrt::hstring name,
    winrt::hstring categoryIcon,
    Windows::Foundation::Collections::IObservableVector<HierarchicalNavigationViewDataBinding::Category> children,
    bool isleaf) : m_name(name), m_categoryIcon(categoryIcon), m_children(children), m_isleaf(isleaf) {}
...
bool Category::IsLeaf()
{
    return m_isleaf;
}

void Category::IsLeaf(bool value)
{
    m_isleaf = value;
}

// MainPage.h and MainPage.cpp
// Delete OnItemInvoked, OnItemExpanding, and OnItemCollapsed.

// MainPage.cpp
...
MainPage::MainPage()
{
    InitializeComponent();

    m_categories = winrt::single_threaded_observable_vector<HierarchicalNavigationViewDataBinding::Category>();

    auto menuItem10 =
        winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
        (L"Menu item 10", L"Icon", nullptr, true);

    auto menuItem9 =
        winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
        (L"Menu item 9", L"Icon", nullptr, true);
    auto menuItem8 =
        winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
        (L"Menu item 8", L"Icon", nullptr, true);
    auto menuItem7Children =
        winrt::single_threaded_observable_vector<HierarchicalNavigationViewDataBinding::Category>();
    menuItem7Children.Append(*menuItem9);
    menuItem7Children.Append(*menuItem8);

    auto menuItem7 =
        winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
        (L"Menu item 7", L"Icon", menuItem7Children);
    auto menuItem6Children =
        winrt::single_threaded_observable_vector<HierarchicalNavigationViewDataBinding::Category>();
    menuItem6Children.Append(*menuItem7);

    auto menuItem6 =
        winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
        (L"Menu item 6", L"Icon", menuItem6Children);

    auto menuItem5 =
        winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
        (L"Menu item 5", L"Icon", nullptr, true);
    auto menuItem4 =
        winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
        (L"Menu item 4", L"Icon", nullptr, true);
    auto menuItem3Children =
        winrt::single_threaded_observable_vector<HierarchicalNavigationViewDataBinding::Category>();
    menuItem3Children.Append(*menuItem5);
    menuItem3Children.Append(*menuItem4);

    auto menuItem3 =
        winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
        (L"Menu item 3", L"Icon", menuItem3Children);
    auto menuItem2Children =
        winrt::single_threaded_observable_vector<HierarchicalNavigationViewDataBinding::Category>();
    menuItem2Children.Append(*menuItem3);

    auto menuItem2 =
        winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
        (L"Menu item 2", L"Icon", menuItem2Children);
    auto menuItem1Children =
        winrt::single_threaded_observable_vector<HierarchicalNavigationViewDataBinding::Category>();
    menuItem1Children.Append(*menuItem2);

    auto menuItem1 =
        winrt::make_self<HierarchicalNavigationViewDataBinding::implementation::Category>
        (L"Menu item 1", L"Icon", menuItem1Children);

    m_categories.Append(*menuItem1);
    m_categories.Append(*menuItem6);
    m_categories.Append(*menuItem10);
}
...

Навигация с помощью клавиатуры в иерархическом представлении NavigationView

Пользователи могут перемещать фокус по NavigationView с помощью клавиатуры. Клавиши со стрелками обеспечивают доступ к "встроенной навигации" внутри панели и следуют предопределенным взаимодействиям в представлении дерева. Ключевые действия изменяются при переходе через NavigationView или во всплывающем меню, которое отображается в режимах Top и Left-compact представления HierarchicalNavigationView. Ниже приведены действия, которые каждая клавиша может выполнить в иерархическом представлении NavigationView:

Key В левом режиме В верхнем режиме Во всплывающем меню
Вверх Перемещает фокус на элемент непосредственно над элементом, на котором фокус находится сейчас. Не выполняет никаких действий. Перемещает фокус на элемент непосредственно над элементом, на котором фокус находится сейчас.
Вниз Перемещает фокус на элемент непосредственно под элементом, на котором фокус находится сейчас.* Не выполняет никаких действий. Перемещает фокус на элемент непосредственно под элементом, на котором фокус находится сейчас.*
Правильно Не выполняет никаких действий. Перемещает фокус на элемент непосредственно справа от элемента, на котором фокус находится сейчас. Не выполняет никаких действий.
Лево Не выполняет никаких действий. Перемещает фокус на элемент непосредственно слева от элемента, на котором фокус находится сейчас. Не выполняет никаких действий.
ПРОБЕЛ/ВВОД Если элемент содержит дочерние элементы, разворачивает или сворачивает элемент и не меняет фокус. Если элемент содержит дочерние элементы, разворачивает дочерние элементы во всплывающий элемент и помещает фокус на первый элемент во всплывающем элементе. Вызывает или выбирает элемент и закрывает всплывающее меню.
Клавиша Esc Не выполняет никаких действий. Не выполняет никаких действий. Закрывает всплывающее меню.

Клавиша пробела или ВВОД всегда вызывает или выбирает элемент.

*Обратите внимание, что элементы не должны быть визуально смежными, фокус будет перемещен от последнего элемента в списке панели к элементу параметров.

Фоны панелей

По умолчанию панель NavigationView использует фоновый цвет в зависимости от режима отображения:

  • при развертывании слева рядом с содержимым (в режиме "Слева") панель имеет сплошной серый цвет.
  • при открытии панели в качестве наложения поверх содержимого используется встроенный акрил (в верхнем, минимальном или компактном режимах).

Чтобы изменить фоновый цвет панели, вы можете переопределить ресурсы темы XAML, используемые для отображения фона в каждом режиме. (Для обеспечения поддержки разных фоновых цветов для различных режимов отображения обычно используется этот метод вместо свойства PaneBackground.)

В приведенной ниже таблице описано использование ресурсов темы в каждом режиме отображения.

Режим отображения Ресурсы темы
Лево ФонРасширеннойПанелиНавигации
ЛевыйКомпактный
ЛевыйМинимальный
ФоновыйЭлементПанелиНавигацииПоУмолчанию
Верх ФонВерхнейПанелиNavigationView

В этом примере показано, как переопределить ресурсы темы в файле App.xaml. При переопределении ресурсов темы всегда необходимо предоставлять как минимум словари ресурсов "Default" и "HighContrast", а также при необходимости словари для ресурсов "Light" или "Dark". Дополнительные сведения см. в статье о свойстве ResourceDictionary.ThemeDictionaries.

Пустое пространство вверху

Приложения, которые настраивают строку заголовка окна , часто расширяют содержимое в области заголовков. Если NavigationView используется вместе с пользовательской строкой заголовка, необходимо убедиться, что его интерактивные элементы (кнопка назад, переключатель панели) не перекрываются с областью перетаскивания заголовка.

Рекомендуемый подход заключается в том, чтобы поместить элемент управления TitleBar над NavigationView и разрешить titleBar обрабатывать кнопку "Назад" и переключатель области. Это шаблон, используемый приложением WinUI 3 Gallery.

Так как элемент управления TitleBar включает кнопку "Назад" и кнопку переключения панели, необходимо скрыть встроенные версии NavigationView, установив IsBackButtonVisible="Collapsed" и IsPaneToggleButtonVisible="False". Затем TitleBar перенаправит эти взаимодействия в NavigationView через обработчики событий:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <TitleBar x:Name="titleBar"
              Title="My App"
              IsPaneToggleButtonVisible="True"
              PaneToggleRequested="TitleBar_PaneToggleRequested"
              IsBackButtonVisible="{x:Bind contentFrame.CanGoBack, Mode=OneWay}"
              BackRequested="TitleBar_BackRequested" />

    <NavigationView x:Name="NavView"
                    Grid.Row="1"
                    IsBackButtonVisible="Collapsed"
                    IsPaneToggleButtonVisible="False"
                    DisplayModeChanged="NavView_DisplayModeChanged">
        <Frame x:Name="contentFrame" />
    </NavigationView>
</Grid>

В вашем коде расширьте содержимое в панели заголовка, назначьте элемент управления TitleBar и свяжите обработчики событий, чтобы обеспечить связь кнопок TitleBar с NavigationView.

this.ExtendsContentIntoTitleBar = true;
this.SetTitleBar(titleBar);
// Toggle the NavigationView pane open or closed when the TitleBar button is clicked.
private void TitleBar_PaneToggleRequested(TitleBar sender, object args)
{
    NavView.IsPaneOpen = !NavView.IsPaneOpen;
}

// Navigate back when the TitleBar back button is clicked.
private void TitleBar_BackRequested(TitleBar sender, object args)
{
    if (contentFrame.CanGoBack)
    {
        contentFrame.GoBack();
    }
}

// Hide the pane toggle button in the TitleBar when NavigationView switches to Top mode,
// since the pane isn't applicable in that layout.
private void NavView_DisplayModeChanged(NavigationView sender,
    NavigationViewDisplayModeChangedEventArgs args)
{
    titleBar.IsPaneToggleButtonVisible =
        sender.PaneDisplayMode != NavigationViewPaneDisplayMode.Top;
}

С помощью этого шаблона элемент управления TitleBar управляет областью перетаскивания и интерактивными кнопками, поэтому NavigationView не нуждается в автоматическом заполнении.

Использование кастомного элемента строки заголовка

Если вы определяете настраиваемую область перетаскивания с помощью Window.SetTitleBar с собственным uiElement вместо элемента управления TitleBar, NavigationView автоматически добавляет заполнение, чтобы предотвратить перекрытие интерактивных элементов области заголовка.

Приложение, расширяющееся за счет строки заголовка

Если вы предпочитаете, чтобы кнопки обратного и меню приближались к верхней части окна приложения, задайте значение IsTitleBarAutoPaddingEnabledfalse:

Приложение, расширяющееся в строку заголовка без дополнительного заполнения

<NavigationView x:Name="NavView" IsTitleBarAutoPaddingEnabled="False">

Настройка поля заголовка

Чтобы дополнительно изменить положение области заголовка NavigationView, переопределите ресурс темы XAML NavigationViewHeaderMargin в ресурсах страницы:

<Page.Resources>
    <Thickness x:Key="NavigationViewHeaderMargin">12,0</Thickness>
</Page.Resources>

Этот ресурс темы изменяет поля вокруг NavigationView.Header.