Xamarin Forms Shell MenuItems - Problem Working with IsVisible - Possible Workaround?

Hello!

I posted this in discussions earlier - allow me to frame it as a question instead (don't mean to spam)

--

I recently came across Xamarin Forms Shell and would like to implement it, but the one caveat I'm having is that, as of now, there is no way to bind the shell's MenuItem's visibility to anything, for the MenuItem doesn't have an IsVisible property.

I would like to be able to show/hide a set of menu items based on whether a user is logged in or not, but I can't seem to figure out how without doing some dirty adding/removing items based on certain events being fired, but it's getting ugly and losing it's xamarin.forms-ness.

A possible workaround I'm trying to implement for the time being is to extend the MenuItem, add a visibility property, and bind to that, but I'm having difficulty working with the property from within the DataTemplate.

The CustomMenuItem's 'IsMenuItemVisible' property binds without problem, but it doesn't seem to be read properly.

Could someone point out to me what am I doing wrong? I'd really appreciate it..

XAML:
"""
Shell ...
xmlns:landing="clr-namespace:App.Pages.Landing"
xmlns:pages="clr-namespace:App.Pages"
x:Class="App.Pages.LandingShell"
x:Name="ParentShell"
NavigationPage.HasNavigationBar="False"
NavigationPage.HasBackButton="False"
Shell.NavBarIsVisible="False">

<pages:CustomMenuItem
    x:Name="LoginMenuItem"
    Text="Login"
    Command="{Binding GoToLoginPageCommand}"
    IsMenuItemVisible="{Binding IsLoginMenuVisible}" />

<pages:CustomMenuItem
    x:Name="CreateAccountMenuItem"
    Text="Create an Account"
    Command="{Binding GoToCreateAccountPageCommand}"
    IsMenuItemVisible="{Binding IsLoginMenuVisible}" />


<pages:CustomMenuItem
    x:Name="GreetingMenuItem"
    Text="{Binding Greeting}"
    IsMenuItemVisible="{Binding IsLogoutMenuVisible}" />

<pages:CustomMenuItem
    x:Name="LogoutMenuItem"
    Text="Logout"
    Command="{Binding LogoutCommand}"
    IsMenuItemVisible="{Binding IsLogoutMenuVisible}" />


<Shell.MenuItemTemplate>
    <DataTemplate>
        <Grid IsVisible="{Binding IsMenuItemVisible}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="0.2*" />
                <ColumnDefinition Width="0.8*" />
            </Grid.ColumnDefinitions>

            <Label Text="{Binding Text}"                       
                   Grid.Column="1"
                   Style="{StaticResource LabelStyle}" />
        </Grid>

    </DataTemplate>
</Shell.MenuItemTemplate>

/Shell>
"""

CS :
"""
namespace App.Pages
{
public partial class LandingShell : Shell, INotifyPropertyChanged
{
public LandingShell()
{
IsLoginMenuVisible = !IsLoggedIn;
IsLogoutMenuVisible = IsLoggedIn;

        // -- Handle Login/Logout view

        MessagingCenter.Subscribe<EventArgs, User>(this, "UpdateMenu", (args, usr) =>
        {
            IsLoggedIn = true;

            Greeting = $"Hello {usr.Username}!";
            IsLoginMenuVisible = !IsLoggedIn;
            IsLogoutMenuVisible = IsLoggedIn;
        });

        MessagingCenter.Subscribe<EventArgs>(this, "UpdateMenuLoggedOut", args =>
        {
            IsLoggedIn = false;

            IsLoginMenuVisible = !IsLoggedIn;
            IsLogoutMenuVisible = IsLoggedIn;

            Greeting = string.Empty;
        });

        BindingContext = this;
        InitializeComponent();
    }

    ...

    private string _greeting;
    public string Greeting
    {
        get => _greeting;
        set
        {
            if (value == _greeting) return;
            _greeting = value;
            OnPropertyChanged(nameof(Greeting));
        }
    }


    private bool _isLoggedIn;
    public bool IsLoggedIn
    {
        get => _isLoggedIn;
        set
        {
            if (value == _isLoggedIn) return;
            _isLoggedIn = value;
            OnPropertyChanged(nameof(IsLoggedIn));
        }
    }

    private bool _isLoginMenuVisible;
    public bool IsLoginMenuVisible
    {
        get => _isLoginMenuVisible;
        set
        {
            if (value == _isLoginMenuVisible) return;
            _isLoginMenuVisible = value;
            OnPropertyChanged(nameof(IsLoginMenuVisible));
        }
    }
    private bool _isLogoutMenuVisible;
    public bool IsLogoutMenuVisible
    {
        get => _isLogoutMenuVisible;
        set
        {
            if (value == _isLogoutMenuVisible) return;
            _isLogoutMenuVisible = value;
            OnPropertyChanged(nameof(IsLogoutMenuVisible));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class CustomMenuItem : MenuItem, INotifyPropertyChanged
{
    public static readonly BindableProperty IsMenuItemVisibleProperty =
        BindableProperty.Create(
            propertyName: "IsMenuItemVisible",
            returnType: typeof(bool),
            declaringType: typeof(CustomMenuItem),
            defaultValue: false,
            defaultBindingMode: BindingMode.TwoWay,
            propertyChanged: IsMenuItemVisiblePropertyChanged);

    public bool IsMenuItemVisible
    {
        get { return (bool)GetValue(IsMenuItemVisibleProperty); }
        set {
            SetValue(IsMenuItemVisibleProperty, value);
            OnPropertyChanged(nameof(IsMenuItemVisible));
        }
    }

    private static void IsMenuItemVisiblePropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        Console.Write("MenuItem Changed");
        var menuItem = (CustomMenuItem)bindable;
        menuItem.IsMenuItemVisible = (bool)newValue;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

}
"""

Thanks in advance!

Best Answer

Answers

  • ColeXColeX Member, Xamarin Team Xamurai
    edited November 2019

    You create the custom BindableProperty to control the visible of MenuItem, but how it works ?

    I can't see the code where IsMenuItemVisible works for.

  • thisismyselfthisismyself Member ✭✭

    A suggested solution would be to create to AppShell xaml files. One for the logged in state, one for the logged out state. But there are still bugs in Xamarin making your App not working correct when you do this.

    In our project we decided to add and remove the Shell Items in code behind, depending on logged in/out state. But there are traps as well (for example automatic navigation when you remove the current display Shell Item). Switching between two AppShell instances or having an IsVisible property would be much preferrable.

  • brouillettebrouillette Member ✭✭

    @ColeX
    There's the class CustomMenuItem as part of the App.Pages namespace at the bottom of the post.

    For clarity:
    public class CustomMenuItem : MenuItem, INotifyPropertyChanged
    {
    public static readonly BindableProperty IsMenuItemVisibleProperty =
    BindableProperty.Create(
    propertyName: "IsMenuItemVisible",
    returnType: typeof(bool),
    declaringType: typeof(CustomMenuItem),
    defaultValue: false,
    defaultBindingMode: BindingMode.TwoWay,
    propertyChanged: IsMenuItemVisiblePropertyChanged);

    public bool IsMenuItemVisible
    {
        get { return (bool)GetValue(IsMenuItemVisibleProperty); }
        set {
            SetValue(IsMenuItemVisibleProperty, value);
            OnPropertyChanged(nameof(IsMenuItemVisible));
        }
    }
    
    private static void IsMenuItemVisiblePropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        Console.Write("MenuItem Changed");
        var menuItem = (CustomMenuItem)bindable;
        menuItem.IsMenuItemVisible = (bool)newValue;
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged(string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    

    }

    Ideally, the CustomMenuItem's IsMenuItemVisible would bind to the Shell's boolean values, and the DataTemplate Grid would bind to the IsMenuItemVisible

    @thisismyself
    Personally, I would want to avoid doing that if possible

  • brouillettebrouillette Member ✭✭
    @thisismyself I meant I'd rather not use two shells.
    And thanks! I'll keep an eye on the GitHub thread
Sign In or Register to comment.