Home > Net >  .net maui custom control handlers, how to convert Content between virtualview and platformview
.net maui custom control handlers, how to convert Content between virtualview and platformview

Time:05-23

I am trying to create a custom Maui Handler for WinUI3 NavigationView.

I have defined the mapping from the virtualView to the PlatformView but i cant seem to convert the Virtual view Content to the PlatformView Content,

An example would be I have a NavigationView.Content = Microsoft.Maui.Frame.Content.

Looking through the maui code there seems to be some mappers but i cant figure out how to get these invoked.

Here is my current code

public static void MapContent(IZtNavigationViewHandler viewHandler, IZtNavigationView virtualView) 
{ 
  ((NavigationView)(viewHandler?.PlatformView)).Content = virtualView.Content; 
}

The mapper is being called but it cant convert the content correctly

Any Ideas or pointers?

CodePudding user response:

TL;DR The lines in a hand-written custom handler’s mapper method, where Content is mapped:

    if (handler.VirtualView.PresentedContent is IView view) 
        handler.PlatformView.Children.Add(view.ToPlatform(handler.MauiContext));

OR if PlatformView has a Content property (NavigationView does), then:

    if (handler.VirtualView.PresentedContent is IView view) 
        handler.PlatformView.Content = view.ToPlatform(handler.MauiContext);

Approach 3 would use that syntax.


First, Frame, ListView, and TableView are "legacy" Renderer-based views. They don't use handlers.

Don't use one of those.

Try ContentView as the virtual view.
In Maui source, ContentViewHandler.Windows.cs is the default behavior.


Given:

public class MyContentView : ContentView
{
}

Goal is to make a handler that maps MyContentView to WinUI3's NavigationView, and MyContentView.Content to NavigationView.Content.


ALTERNATIVE 1 - MyContentViewHandler

Similar to Customize specific control instances,
and similar to Maui source solution 'Microsoft.Maui' /
Controls / samples / Maui.Controls.Sample / Controls / BordelessEntry /
BordelessEntryHandler.cs.

One significant difference: Instead of mapping directly to a NavigationView, let MyContentView create ContentPanel (logic inherited from ContentViewHandler), then make NavigationView the child of ContentPanel.

using Microsoft.Maui;
using Microsoft.Maui.Handlers;
...
public class MyContentViewHandler : ContentViewHandler
{
    public static IPropertyMapper<IContentView, IContentViewHandler> Mapper =
        new PropertyMapper<IContentView, IContentViewHandler>(ViewMapper)
        {
            [nameof(IContentView.Content)] = MapContent,
        };
            
    public static void MapContent(IContentViewHandler handler, IContentView page)
    {
        UpdateContent(handler);
    }

    static void UpdateContent(IContentViewHandler handler)
    {
        _ = handler.PlatformView ?? throw new InvalidOperationException($"{nameof(PlatformView)} should have been set by base class.");
        _ = handler.VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class.");
        _ = handler.MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class.");

        handler.PlatformView.Children.Clear();

        if (handler.VirtualView.PresentedContent is IView view)
        {
            // platform equivalent to Content.
            var platformContent = view.ToPlatform(handler.MauiContext);
    #if WINDOWS
            // Wrap content in a NavigationView.
            var navView = new NavigationView();
            navView.Content = platformContent;
            handler.PlatformView.Children.Add(navView);
    #else
            handler.PlatformView.Children.Add(platformContent);
    #endif
        }
    }
}

ALTERNATIVE 2 - Mapper.AppendToMapping

INCOMPLETE ... not sure how/where to do this ...

Technique: AFTER standard mapping is performed, "fix up" MyContentView.

using Microsoft.UI.Xaml.Controls;

Microsoft.Maui.Handlers.ContentViewHandler.Mapper.AppendToMapping(
    "MyContentViewAsNavView", (handler, view) =>
    {
        if (view is MyContentView)
        {
    #if WINDOWS
            if (handler.PlatformView.Children.Count > 0) {
                // ASSUME first child is the Content to wrap.
                var platformContent = handler.PlatformView.Children[0];
                // Wrap content in a NavigationView.
                var navView = new NavigationView();
                navView.Content = platformContent;
                handler.PlatformView.Children[0] = navView;
            }
    #endif
        }
    });

ALTERNATIVE 3 - "NavigationPanel" adapter

(Instead of "nesting" NavigationView inside of ContentPanel.)

  • Copy Maui sources ContentViewHandler.cs and ContentViewHandler.Windows.cs, to MyContent... files.
  • Replace "ContentView" with "MyContentView" and "ContentPanel" with "NavigationView".
  • Observe that it won't compile. "CrossPlatformMeasure" and "CrossPlatformArrange" don't exist in "NavigationView".
  • Make a new class "NavigationPanel" to adapt "NavigationView". Equivalent of "ContentPanel" which adapts "Panel" for ContentView. Adapt from Microsoft.Maui.Platform.ContentPanel source.
  • Where you previously replaced "ContentPanel" with "NavigationView", instead use this new "NavigationPanel".

CodePudding user response:

Thanks to ToolmakerSteve I found out a direction to do it in.

The ControlView must inherit from both View and IContentView,

// So the interface would be 
 [ContentProperty("Content")]
public interface IMyControlView : IContentView
{}

// Then the Control must be constructed with 
public partial class MyControlView : View, IContentView, IMyControlView
{

// Then in the view implement the MapContent

public object Content 
        {
            get { return (View)GetValue(ContentProperty); }
            set { SetValue(ContentProperty, value); }
        }
// you must also implement PresentedContent
IView? IContentView.PresentedContent => (View)Content;
}

Then in your handler do the mapping for Content and expose it as

public partial class MyControlViewHandler : ViewHandler<MyControlView, ActualControl>, IMyControlViewHandler    
{
        public static void MapContent(IZtNavigationViewHandler handler, IZtNavigationView virtualView)
            {
                //((NavigationView)(viewHandler?.PlatformView)).Content = virtualView.Content;
                _ = handler.PlatformView ?? throw new InvalidOperationException($"{nameof(PlatformView)} should have been set by base class.");
                _ = handler.VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class.");
                _ = handler.MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class.");
    
    #if WINDOWS
                if (handler.VirtualView.PresentedContent is IView view)
                    handler.PlatformView.Content = view.ToPlatform(handler.MauiContext);
    #endif
            }
}

With this i was able to use the WinUI3 NavigationView with Content from ScrollView, BorderView, ContentView.

More to test but thanks for all the help

  • Related