Search code examples
c++user-interfaceuwpc++-winrt

How to dynamically define Headers and Settings for UWP NavigationView's menu, using C++/winRT?


I'm working on a cross-platform project in C++ generating the UI dynamically, and I am struggling with C++/winRT UWP NavigationView on two problems:

  1. When defining a NavigationViewItemHeader, the resulting header title doesn't show in the navigation menu, the space remains empty,
  2. When trying to update the SettingsItem of the navigation menu, the value of the Settings navigation item is nullptr as returned by SettingsItem().

Here is the code I wrote for generating the menu from a list of items managed independently from the host (e.g. Windows):

bool
CANavigationView::UpdateHostView( void )
{
    TNavigationItemPtr item;
    TIndex index;

    if( _hostViewUpdateNeeded == false )
        return false;

    Windows::UI::Xaml::Controls::NavigationViewItemBase hItem( nullptr );
    Windows::UI::Xaml::Controls::TextBlock hText( nullptr );
    winrt::hstring hTag;

    // Remove all navigation items from the current host view:
    _hostNavigationView.MenuItems().Clear();
    _hostNavigationView.IsSettingsVisible( false );

    // Build the navigation menu items:
    for( index = 0; index < _navigationItems.CountOfItems(); index++ )
    {
        item = * _navigationItems.GetItemAtIndex( index );

        if( item->identifier == kSettingsItem )
        {
            _hostNavigationView.IsSettingsVisible( true );
            hText = Windows::UI::Xaml::Controls::TextBlock();
            CSString::ConvertToUIString( item->title->GetString( gAppLanguageCode ), & hTag );
            hText.Text( hTag );
//          Issue #1 : cannot access to the Settings item
//          _hostNavigationView.SettingsItem().as< Windows::UI::Xaml::Controls::NavigationViewItem >().Content( hText );
//          SettingsItem() returns nullptr...
        }
        else
        {
            switch( item->type )
            {
                case eNavigationHeader:
                    hItem = Windows::UI::Xaml::Controls::NavigationViewItemHeader();
                    hText = Windows::UI::Xaml::Controls::TextBlock();
                    CSString::ConvertToUIString( item->title->GetString( gAppLanguageCode ), & hTag );
                    hText.Text( hTag );
//                  Issue #2: The header's title is not displayed
                    hItem.Content( hText );
                    _hostNavigationView.MenuItems().Append( hItem );
                    break;

                case eNavigationSeparator:
                    hItem = Windows::UI::Xaml::Controls::NavigationViewItemSeparator();
                    _hostNavigationView.MenuItems().Append( hItem );
                    break;

                case eNavigationItem:
                    hItem = Windows::UI::Xaml::Controls::NavigationViewItem();
                    CSString::ConvertToUIString( CAUIElement::GetStringFromUIIdentifier( item->identifier ), & hTag );
                    hItem.Tag( winrt::box_value( hTag ) );
                    hText = Windows::UI::Xaml::Controls::TextBlock();
                    CSString::ConvertToUIString( item->title->GetString( gAppLanguageCode ), & hTag );
                    hText.Text( hTag );
                    hItem.Content( hText );
                    hItem.as< Windows::UI::Xaml::Controls::NavigationViewItem>().Icon( GetHostIcon( item->icon ) );
                    _hostNavigationView.MenuItems().Append( hItem );
                    break;

                default:
                    break;
            }
        }
    }

    _hostViewUpdateNeeded = false;

    return true;
}

As I'm using my own string format (I'm stuck in old C++ standards...) and I18N support, I need to first convert the UTF8 string to the host (here Windows) before setting the value of the text block, using the hTag variable of type hstring. In debugging mode, the text is well transcoded in the hstring format...

What is puzzling me is the fact that both NavigationSeparator and NavigationItem cases are working fine, in line with the official Microsoft documentation (including the Tag for menu event handling and Icon setting for NavigationViewItem).

I understand this is not the "mainstream XAML way" of working on UWP user interface but so far the approach is working well on other UI elements.

Here is a screenshot of the navigation view with the empty spaces for the headers:

Actual NavigationView

Also, in the example above, I logged the number of menu items in the host navigation view (_hostNavigationView.MenuItems().Size()) and got 7 as a result, which is correct...

At last, here's the detailed log I'm generating in DEBUG mode:

DBG-[000002686A230710]CANavigationView::UpdateDisplayedLanguage() {
    DBG-[000002686A230710]CANavigationView::UpdateHostView() {
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Navigation item 0, type 2
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Header case: Reference Library
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Navigation item 1, type 1
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Navigation item case
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Navigation item 2, type 1
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Navigation item case
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Navigation item 3, type 1
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Navigation item case
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Navigation item 4, type 3
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Separator case
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Navigation item 5, type 2
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Header case: Project Library
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Navigation item 6, type 1
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Navigation item case
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Navigation item 7, type 1
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Settings case
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Value of SettingsItem(): 0000000000000000
        DBG-[000002686A230710]CANavigationView::UpdateHostView() Count of menu items for the navigation view: 7 (8)
    DBG-}
DBG-}

Your help in solving those two issues would be greatly appreciated !

Best regards,

Arnaud


Solution

  • Based on Roy's comments, it is not necessary to use TextBlock to set the value of both NavigationViewItemHeader and NavigationViewItem. Instead, it is just a case of boxing the string value into an IInspectable object:

    hItem.Content( winrt::box_value( hTag ) );
    

    Now, I have the correct display and behavior of the navigation menu:

    enter image description here

    Thank you, Roy !

    UPDATE: I also managed to change the title of the SettingsItem. According to the documentation, a good time to customize the navigation menu is when the view is loaded.

    Therefore, I subscribed/registered to the Loaded() event and performed the customization from there. At that stage of the lifecycle of the navigation view, SettingsItem() returns a valid NavigationViewItem allowing me to change the title with the same string boxing approach.

    Both problems are solved now!