Search code examples
c#winui-3windows-app-sdk

Cannot instantiate a Page or User Control w/ NavigationView from a WinUI Class Library in WinForms Islands (experimental)


I have a WinUI 3 Class Library containing a page. I try to load these two in a grid in a separate WinForms application, hosting a WinUI Island. If the page contains a NavigationView object, the constructor of the page crashes with:

Microsoft.UI.Xaml.Markup.XamlParseException: 'XAML parsing failed.'

If the page contains something else, for example, a list box, it is loaded without any problems.

Loading the page:

var c = new MyWinUILibrary.MyFabulousPage();
gridHost.Children.Add(c);

This works:

<Page
    x:Class="MyWinUILibrary.MyFabulousPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyWinUILibrary"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <ListBox Width="200">
            <x:String>Blue</x:String>
            <x:String>Green</x:String>
            <x:String>Red</x:String>
            <x:String>Yellow</x:String>
        </ListBox>
    </Grid>
</Page>

This doesn't:

<Page
    x:Class="MyWinUILibrary.MyFabulousPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyWinUILibrary"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <NavigationView x:Name="navigationView" IsSettingsVisible="True">
            <NavigationView.MenuItems>
                <NavigationViewItem Icon="NewFolder" Content="Menu item 1" />
            </NavigationView.MenuItems>
            <Frame x:Name="frameContent" />
        </NavigationView>
    </Grid>
</Page>

I have tried with both a user control instead of a page and observed the same behavior. Using a NavigationView causes the constructor to crash.

If I embed the NavigationView in Xaml, all works properly.

Further, if I create WinUI controls at runtime, I can create a grid with a button or list box and add it to the island. If I create a grid with a Navigation view, the app crashes when I try to set 'DesktopWindowXamlSource.Content' with a COMException with no message, somewhere in the depths of WinRT.


Initially, I erroneously thought it was crashing when loading the library in WinUI app. It doesn't. Obviously I had something else wrong on my first try. Recreating the host app helped.


Solution

  • What's missing in the Winform's project hosting WinUI3 XAML islands is full XAML resolution, so NavigationView and its dependencies are not fully available. This is easily solved in WinUI3 app as described here: Windows.UI.Xaml.Markup.XamlParseException on NavigationView

    But by default you don't have a WinUI3 application since you're running a Winforms application. The solution is more or less (more less than more...) touched somehow in old UWP articles on XAML islands like Use XAML Islands to host a UWP XAML control in a C# WPF app but you still have to connect the bits.

    So you must create a WinUI3 app and this app must implement the IXamlMetadataProvider Interface

    implements XAML type resolution and provides the mapping between types used in markup and the corresponding classes implemented in an application or component.

    Fortunately this is already implemented by the XamlControlsXamlMetaDataProvider Class (which is not documented, it would be too easy...) to which you can forward IXamlMetadataProvider methods.

    So, here is some sample code that you can use to start a WinUI3 app (aside the Winforms app, they can cohabit) capable of parsing what's needed here:

    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Markup;
    using Microsoft.UI.Xaml.XamlTypeInfo;
    
    internal static class Program
    {
        [STAThread]
        static void Main()
        {
            // add this here
            // and BTW, this instance will become Microsoft.UI.Xaml.Application.Current
            // which is null otherwise
            _ = new MyApp();
    
            ApplicationConfiguration.Initialize();
            Application.Run(new MainForm());
        }
    }
    
    public class MyApp : Microsoft.UI.Xaml.Application, IXamlMetadataProvider
    {
        private readonly XamlControlsXamlMetaDataProvider _provider = new();
    
        protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
        {
            // equivalent of this in an App.xaml file
            //
            // <Application ...
            //    <Application.Resources>
            //        <ResourceDictionary>
            //            <ResourceDictionary.MergedDictionaries>
            //                <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
            //            </ResourceDictionary.MergedDictionaries>
            //        </ResourceDictionary>
            //    </Application.Resources>
            // </Application>
            //
    
            Resources.MergedDictionaries.Add(new XamlControlsResources());
            base.OnLaunched(args);
        }
    
        public IXamlType GetXamlType(Type type) => _provider.GetXamlType(type);
        public IXamlType GetXamlType(string fullName) => _provider.GetXamlType(fullName);
        public XmlnsDefinition[] GetXmlnsDefinitions() => _provider.GetXmlnsDefinitions();
    }