dontspeak
dontspeak

Reputation: 11

Access ContentPage from IMarkupExtension

In my ContentPage, I'm using an IMarkupExtension to localize Strings. How can I access the root ContentPage from the IMarkupExtension ProvideValue() Method?

Within the ProvideValue() method, I see no possibility to access the ContentPage object. But for error handling etc, I need to access some members of the ContentPage object.

ContentPage:

<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    [...]
    x:Name="Page1"
    >

    <ToolbarItem [...] Text="{local:Translate MENUE_LOGIN}"  />

    [...]
</ContentPage>

IMarkupExtension:

[ContentProperty("Text")]
public class TranslateExtension : IMarkupExtension
{
    [...]
    public string Text { get; set; }

    public object ProvideValue(IServiceProvider serviceProvider)
    {
         // How can I access Page1 here???
    }
}

As I don't can insert an object reference via constructor, how I can access the ContentPage at all to use its methods?

Upvotes: 0

Views: 255

Answers (2)

Sharada
Sharada

Reputation: 13601

Update 03/30/20 - Use reflection to access non-public ParentObjects

You can use serviceProvider to access the service that implements IProvideParentValues; which in turn should be able to provide the parent-object(s) for target element.

Caution: This solution uses reflection, so use this option sparingly - as it might break with later versions of Xamarin.Forms

Note: Only tested with current version Xamarin.Forms <= 4.5.0

For example:

private PropertyInfo cachedPropertyInfo = null;

public object ProvideValue(IServiceProvider serviceProvider)
{
    if (serviceProvider == null)
        throw new ArgumentNullException(nameof(serviceProvider));
        
    var valueProvider = serviceProvider.GetService<IProvideValueTarget>()    
                            ?? throw new ArgumentException("serviceProvider does not provide an IProvideValueTarget");

    cachedPropertyInfo = cachedPropertyInfo ??
        valueProvider.GetType().GetProperty("Xamarin.Forms.Xaml.IProvideParentValues.ParentObjects", BindingFlags.NonPublic | BindingFlags.Instance);

    if(cachedPropertyInfo != null)
    {
        var parentObjects = cachedPropertyInfo.GetValue(valueProvider) as IEnumerable<object>;
        if (parentObjects == null)
            throw new ArgumentException("Unable to access parent objects");

        foreach (var target in parentObjects)
        {
            if (!(target is Page page))
                continue;

            // ---->>> Access target here for root parent page.
        }
    }

    throw new XamlParseException($"Unable to access parent page");
}

This is how StaticResource or Reference extensions access the parent or root objects.

Ref: https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Xaml/MarkupExtensions/StaticResourceExtension.cs https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Xaml/MarkupExtensions/ReferenceExtension.cs

Upvotes: 1

TaylorD
TaylorD

Reputation: 687

While I don't always agree with using this method of notifying other classes and such via this method, this might be the best situation for you. Look into MessagingCenter. This allows you to register strings as an event and when you notify the Exception event, your page can listen to that event and do the appropriate action(s).

Here is the documentation for Messaging Center

If you aren't displaying the error to the user and just logging it to a file or AppCenter, you can register to listen for the particular MessagingCenter action via your logger class.

Upvotes: 0

Related Questions