-1

My exact problem is I am trying to add logic to all the pages in my Xamarin app, globally.

That is, I am trying to create a "master page" that all my pages inherit from. I put header/footer and navigation logic in this "master page" so that I don't have to rewrite it on every page. ...I say "master page" because Xamarin has something called a master-detail page and that's not what I'm talking about. I'm talking about the concept of a master page like you might have used in ASP.NET Web Forms. The closest analog to that in Xamarin is <ControlTemplate> which, with a little work on your part, gives pretty much the same effect as what you might actually think of as a master page. It kind of looks like this:

App.xaml (the body of the master page)

...
<Application.Resources>
    <ResourceDictionary>
        <ControlTemplate x:Key="MainPageTemplate">
            <StackLayout>
                <Label Text="{TemplateBinding HeaderText}" />
                <ContentPresenter /> <!-- this is a placeholder for content -->
                <Label Text="{TemplateBinding FooterText}" />
            </StackLayout>
        </ControlTemplate>
    </ResourceDictionary>
</Application.Resources>
...

MyPage.xaml (the content that goes in the master page)

<!-- Translation: this page inherits from CoolApp.Views.MyMasterPage.cs -->
<Views:MyMasterPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            xmlns:Views="clr-namespace:CoolApp.Views"
            x:Class="CoolApp.Views.MyPage"
            ControlTemplate="{StaticResource MainPageTemplate}">
    <Label Text="I am the content" />
</Views:MyMasterPage>

MyMasterPage.cs (the shared header/footer and navigation logic)

// it inherits from ContentPage here, which works fine. so long as your app only uses ContentPages
public class MyMasterPage : ContentPage, INotifyPropertyChanged
{
    ...
    public static BindableProperty HeaderTextProperty;

    public string HeaderText
    {
        get { return (string)GetValue(HeaderTextProperty); }
        set
        {
            HeaderTextProperty = BindableProperty.Create("HeaderText", typeof(string), typeof(SearchPage), value);
            OnPropertyChanged("HeaderText");
        }
    }
    // footer logic
    // navigation logic
    ...
}

This master page setup works great for me until I need to use page types other than ContentPage. Specifically, I need a CarouselPage to work exactly the same way. In the above code I could have MyMasterPage.cs inherit from CarouselPage instead of ContentPage and that would be fine except then I would have the global header/footer and navigation logic in two different master pages. I can't just put the logic in a master page that inherits from Page because then my pages would be of type Page instead of ContentPage and CarouselPage. But if I could modify Page directly then ContentPage and CarouselPage would both get my changes. Maybe that would sort of look like this?

enter image description here

I hope it's clear but in case it's not the question I am trying to ask is how can I get my header and footer and navigation logic all into a single master page?

user875234
  • 2,399
  • 7
  • 31
  • 49

2 Answers2

3

I hear add functionality to a superclass and think extensions. I don't know anything about c# but, for instance, in swift you can add behavior to an existing type. This isn't achieved by modifying a child, however, but you can give a superclass new abilities. Not sure if this is exactly what you might be looking for in your use case https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods

nbpeth
  • 2,967
  • 4
  • 24
  • 34
  • 1
    Unfortunately that only gets me half way there because you can't add properties through extension methods. – user875234 Jul 26 '18 at 15:44
  • 1
    rats. what about runtime reflections? – nbpeth Jul 26 '18 at 16:12
  • https://stackoverflow.com/questions/14724822/how-can-i-add-properties-to-a-class-on-runtime-in-c – nbpeth Jul 26 '18 at 16:13
  • 1
    That probably would work if I wrestled with it enough but they'd revoke my developer card. Good ideas though, thanks. – user875234 Jul 26 '18 at 17:15
  • yeah I didn't want to be the guy that said 'this is violating the core concepts of inheritance', because I hate that guy. I'm not here to question your motives :D – nbpeth Jul 26 '18 at 17:17
2

I have the same reasoning as nbpeth with extensions

if you want to add a property to all your pages (here MyContentPage and MyCarouselPage), you may want to create a single master page for each instead of only one master page, but centralize the navigation logic in an extension class

you could try something like the following (I do not guaranty this code works, I have no Xamarin setup at hand)

public static class PageExtensions
{
    public string GetMyValue(this Page page, BindableProperty headerTextProperty)
    {
        return (string)page.GetValue(headerTextProperty);
    }

    public void SetMyValue(this Page page, out BindableProperty headerTextProperty, string value)
    {
        headerTextProperty = null;
        if (page is INotifyPropertyChanged notifyPage)
        {
            headerTextProperty = BindableProperty.Create("HeaderText", typeof(string), typeof(SearchPage), value);
            notifyPage.OnPropertyChanged("HeaderText");
        }
    }
}

public class MyMasterContentPage : ContentPage
{
    // ...
    public BindableProperty HeaderTextProperty;

    public string HeaderText
    {
        get { return GetMyValue(HeaderTextProperty); }
        set { SetMyValue(out HeaderTextProperty, value); }
    }
}

public class MyContentPage : MyMasterContentPage, INotifyPropertyChanged
{
}

public class MyMasterCarouselPage : CarouselPage
{
    // ...
    public BindableProperty HeaderTextProperty;

    public string HeaderText
    {
        get { return GetMyValue(HeaderTextProperty); }
        set { SetMyValue(out HeaderTextProperty, value); }
    }
}

public class MyCarouselPage : MyMasterCarouselPage, INotifyPropertyChanged
{
}

You would still have some duplicate code but not duplicate logic

AdricoM
  • 579
  • 4
  • 11
  • 1
    This is kind of the same thing I was thinking. Get them separated into separate master classes first and see what options presented themselves from there. That last little (er, apparently huge) bit about duplicate properties though feels like a deal breaker. I need to spend more time staring at it. – user875234 Jul 26 '18 at 17:19
  • 1
    I marked yours as the answer because it was closest to what I ended up doing. So thanks to this guys answer https://stackoverflow.com/a/39647085/3245937 I was able to get rid of the Header/Footer binding and just set them in the extension method. The navigation logic was able to go in an extension method (or rather, I ended up not needing it after all). So now right after InitializeComponent(); I call this.SetMasterPage("My Header"); and I get the same thing, all through a couple extension methods. – user875234 Jul 26 '18 at 19:20