Found a way to make ToolBarItems Visible/Invisible..without a custom renderer

_PK__PK_ USMember ✭✭
edited May 2015 in Xamarin.Forms

So from what I've seen in the API Docs (http://iosapi.xamarin.com/?link=T:Xamarin.Forms.MenuItem/*) Toolbar items don't seem to have an IsVisible property.

I was able to accomplish this by defining a Binding for the "Icon" property and settings it to null when I don't want the toolbar item to display. So far, it has worked well.

Posting here so that anyone else looking to toggle visibility of toolbar items can try it out.

Posts

  • adamkempadamkemp USInsider, Developer Group Leader mod

    No, that's not supported yet. Also, making an invisible icon doesn't stop it from being tapped.

  • AdamPAdamP AUUniversity ✭✭✭✭✭

    The best way to do this is to create the toolbar items, you can keep them cached/stored in a dictionary and Add and Remove them as needed depending upon functionality present. There isn't a need to make them invisible.

  • alemarkoalemarko HRMember

    Hi, I also stumbled into this. I needed the Toolbar to refresh after changing
    item's title/name, or after adding/removing toolbar items.
    So after I make changes I invoke this small Page extension method.

    Disclaimer: tested and used only on Android and Xamarin.Forms 1.2.3.

    public static void ForceToolbarRedraw(this Page page) { var itemList = page.ToolbarItems.ToList(); // create copy of toolbar items page.ToolbarItems.Clear(); // remove all itemList.ForEach(ti => page.ToolbarItems.Add(ti)); // add all from list }

  • CaptainXamtasticCaptainXamtastic GBUniversity ✭✭✭

    @AdamP said:
    The best way to do this is to create the toolbar items, you can keep them cached/stored in a dictionary and Add and Remove them as needed depending upon functionality present. There isn't a need to make them invisible.

    Adam, do you have an example of how you did this, MVVM friendly if possible!

  • AdamPAdamP AUUniversity ✭✭✭✭✭
    edited December 2015

    @AnthonyHarrison.3194 - The MVVM XAML way to make ToolbarItems Visible/Not Visible (technically its removing and adding them, as you can't actually make them invisible).

    #1. Create a new Control that inherits from ToolbarItem

          public class BindableToolbarItem: ToolbarItem
          {
    
            public BindableToolbarItem()
            {
                InitVisibility();
            }
    
            private async void InitVisibility()
            {
                OnIsVisibleChanged(this, false, IsVisible);
            }
    
            public new ContentPage Parent { set; get; }
    
            public bool IsVisible
            {
                get { return (bool)GetValue(IsVisibleProperty); }
                set { SetValue(IsVisibleProperty, value); }
            }
    
            public static BindableProperty IsVisibleProperty =
                BindableProperty.Create<BindableToolbarItem, bool>(o => o.IsVisible, false, propertyChanged: OnIsVisibleChanged);
    
            private static void OnIsVisibleChanged(BindableObject bindable, bool oldvalue, bool newvalue)
            {
                var item = bindable as BindableToolbarItem;
    
                if (item.Parent == null)
                    return;
    
                var items = item.Parent.ToolbarItems;
    
                if (newvalue && !items.Contains(item))
                {
                    items.Add(item);
                }
                else if (!newvalue && items.Contains(item))
                {
                    items.Remove(item);
                }
              }
          }
    

    #2. In your content pages attributes add this

    x:Name="this"

    and

    xmlns:control="clr-namespace:Mobile.Controls"

    #3. Add in your bindable toolbar items

    <ContentPage.ToolbarItems>
           <control:BindableToolbarItem Text="Delete" Icon="delete.png" Command="{Binding DeleteCommand}" Parent="{x:Reference this}" IsVisible="{Binding IsVisible}" />
      </ContentPage.ToolbarItems>
    
  • ThomasBurkhartThomasBurkhart DEMember ✭✭✭✭
    edited January 2016

    Wow, this looks excalty like what I was looking for.
    @AdamP why do you have to set the Parent manually? Doesn't ToolbarItems have valid Parent?

  • AdamPAdamP AUUniversity ✭✭✭✭✭

    @ThomasBurkhart - why yes it does, thanks for the pickup. With that said you can remove the XAML reference to the parent and change accessing the parent to

    var items = ((ContentPage)item.Parent).ToolbarItems;

  • ThomasBurkhartThomasBurkhart DEMember ✭✭✭✭

    @AdamP
    I just stumbled upon this solution: http://stackoverflow.com/questions/27803038/disable-toolbaritem-xamarin-forms
    Did you ever try this?

  • AdamPAdamP AUUniversity ✭✭✭✭✭

    @ThomasBurkhart - I haven't tried using the CanExecute option, so I am not sure if it provides a disabled look about it when it is off, sounds like it does. But it would be easy to test out, just set the CanExecute parameter in the command you bind to.

  • ThomasBurkhartThomasBurkhart DEMember ✭✭✭✭

    I just tested it. It does not change the look of the icon, it just disables the Event :-(
    So your solution is still the way to go.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    If the result of your CanExecute callback changes you need to call ChangeCanExecute on the command object to notify any controls to update their appearance.

  • ThomasBurkhartThomasBurkhart DEMember ✭✭✭✭

    @adamkemp Sorry, but there is no Method ChangeCanExecute in the ICommand Interface that I could call.

  • ThomasBurkhartThomasBurkhart DEMember ✭✭✭✭

    @AdamP
    I just tried what you said

    @ThomasBurkhart - why yes it does, thanks for the pickup. With that said you can remove the XAML reference to the parent and change accessing the parent to

    var items = ((ContentPage)item.Parent).ToolbarItems;

    But item.parent is always null. No Idea why.

  • ThomasBurkhartThomasBurkhart DEMember ✭✭✭✭
    edited January 2016

    @AdamP
    I found a strange behavior in your solution. When I try to make the Icon invisible at the beginning by assigning false to my bound IsVisible Property in my ViewModell constructor nothing happens, the Icon is displayed.

    When modifying the IsVisible Property later in a TextChanged Event the Icon disappears and appears as expected.

    I also bound the Visibility of a normal Button to the same Property and there is works.


    I now found out, that if I change the bound Property twice it works:

                if (message=="NewEntry")
                {
                    SaveEnabled = true;
                    SaveEnabled = false;
                }
    
  • adamkempadamkemp USInsider, Developer Group Leader mod

    The ChangeCanExecute method is on the Command class. The interface has an event that fires to signal that the value of CanExecute changed, and the ChangeCanExecute method fires that event. If you're using a different implementation of the ICommand interface then just fire the event instead.

  • ThomasBurkhartThomasBurkhart DEMember ✭✭✭✭

    I changed it now to a standard Command:

    ``` SaveCommand = new Command(Save,()=>false);

    and later on

                SaveCommand.ChangeCanExecute();
    

    ```

    Only thing that happens is that the ActionbarIcon does no longer react when tapped, but it stays visible. SO it looks like that this approach does not lead to the desired goal.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Expected (by me) behavior is that the toolbar buttons remain visible, but appear disabled. If it's not doing that then file a bug report here: http://bugzilla.xamarin.com

  • ThomasBurkhartThomasBurkhart DEMember ✭✭✭✭

    Are you running IOS or Android?

  • adamkempadamkemp USInsider, Developer Group Leader mod

    That's the expected behavior for all platforms.

  • ThomasBurkhartThomasBurkhart DEMember ✭✭✭✭

    Ah, sorry, misunderstood you. your wrote "expected", not observed by you.

  • AdamPAdamP AUUniversity ✭✭✭✭✭

    @ThomasBurkhart - I actually experience the issue as well. I also (as a hack) place a delay then set the boolean value. But it has to be because some events are out of order, though haven't had a look to what I need to wait for before setting it.

  • ThomasBurkhartThomasBurkhart DEMember ✭✭✭✭

    @AdamP
    So far setting the property twice works for me well. But it would be good to know where the problem is as it works with a standard Button.

  • ShaneNeuvilleShaneNeuville USUniversity ✭✭

    There's a bug here for it
    https://bugzilla.xamarin.com/show_bug.cgi?id=25661
    Seems like there's been some back and forth about confirming

    i posted a sample in there of the issue with Android and the icon not graying out.. Hopefully that one works (doesn't work) for them

  • ThomasBurkhartThomasBurkhart DEMember ✭✭✭✭

    Have you seen the last Comment https://bugzilla.xamarin.com/show_bug.cgi?id=25661#c12
    There is a solution though a real cumbersome. And nothing said about IOS

  • MichaelRumplerMichaelRumpler ATMember ✭✭✭✭✭

    Normal Buttons also don't appear disabled when CanExecute returns false. I thought that that was the whole purpose of Xamarins own implementation of Command, but it seems like they don't use it at all.

  • LucasLFLucasLF BRMember ✭✭
    edited February 2016

    PK there's my solution for that problem.

    I put the toolbaritems in my xaml like that:

    <TabbedPage.Children>
        <ContentPage x:Name="groupPage" Title="Grupo" BackgroundColor="{StaticResource PrimaryColorLight}">
            <ContentPage.ToolbarItems>
                <ToolbarItem Name="Editar" Order="Primary" Priority="0" Command="{ Binding EditCommand }">
                </ToolbarItem>
            </ContentPage.ToolbarItems>
    

    In my view I subscribe my ViewModel:

    public partial class GroupDetailView : TabbedPage
    {
        GroupDetailViewModel groupDetailViewModel;
        public GroupDetailView (int id)
        {
            groupDetailViewModel = new GroupDetailViewModel (id);
            this.BindingContext = groupDetailViewModel;
            InitializeComponent ();
    
            MessagingCenter.Subscribe<GroupDetailViewModel, bool> (this, "GroupLoaded", (sender, arg) => {
                if (!arg) {
                    groupPage.ToolbarItems.Clear();
                    matchPage.ToolbarItems.Clear();
                }
            });
        }
    }
    

    And finally in my ViewModel I send the content:

    MessagingCenter.Send<GroupDetailViewModel, bool> (this, "GroupLoaded", this.Group.GroupUserLogged.IsAdmin);
    

    If my logged user is not an admin of the current group, hide the toolbaritems.

  • ChrisMcBride.2625ChrisMcBride.2625 USUniversity ✭✭

    @ThomasBurkhart said:
    @AdamP
    I just tried what you said

    @ThomasBurkhart - why yes it does, thanks for the pickup. With that said you can remove the XAML reference to the parent and change accessing the parent to

    var items = ((ContentPage)item.Parent).ToolbarItems;

    But item.parent is always null. No Idea why.

    It is probably too late, but it looks like what is happening is OnIsVisibleChanged is being called when the BinableToolbarItem is being constructed. It hasn't been attached to a parent yet.
    Override OnParentSet():

        protected override void OnParentSet()
        {
            base.OnParentSet();
            InitVisibility();
        }
    
  • Malcolm.JackMalcolm.Jack ZAMember ✭✭

    @ThomasBurkhart said:
    @AdamP
    So far setting the property twice works for me well. But it would be good to know where the problem is as it works with a standard Button.

    "Setting the property twice" - is the cause not perhaps that the bindable property is not defaulted to true (the initial state of the button)?

    For those interested in code, I have created a github repo here

  • voidstreamvoidstream FRMember ✭✭✭

    @NMackay said:
    @AdamP

    Thanks for this adam, was really useful. I updated it slightly to make it support the new format for bindable properties, I also got an error in my app for changing the list so just made sure that was done on the UI thread. Works a treat.

    Thanks again.

    public class BindableToolbarItem : ToolbarItem
        {
            public static readonly BindableProperty IsVisibleProperty =
                BindableProperty.Create("BindableToolbarItem", typeof (bool), typeof (ToolbarItem),
                    true, BindingMode.TwoWay, propertyChanged: OnIsVisibleChanged);
    
            public BindableToolbarItem()
            {
                InitVisibility();
            }
    
            public bool IsVisible
            {
                get { return (bool) GetValue(IsVisibleProperty); }
                set { SetValue(IsVisibleProperty, value); }
            }
    
            private void InitVisibility()
            {
                OnIsVisibleChanged(this, false, IsVisible);
            }
    
            private static void OnIsVisibleChanged(BindableObject bindable, object oldvalue, object newvalue)
            {
                var item = bindable as BindableToolbarItem;
    
                if (item != null && item.Parent == null)
                    return;
    
                if (item != null)
                {
                    var items = ((ContentPage) item.Parent).ToolbarItems;
    
                    if ((bool) newvalue && !items.Contains(item))
                    {
                        Device.BeginInvokeOnMainThread(() => { items.Add(item); });
                    }
                    else if (!(bool) newvalue && items.Contains(item))
                    {
                        Device.BeginInvokeOnMainThread(() => { items.Remove(item); });
                    }
                }
            }
    

    Always great @NMackay :)

  • NMackayNMackay GBInsider, University ✭✭✭✭✭

    I modified this code slightly as it didn't work correctly with tabbed pages having toolbar buttons.

    namespace Foobar.CustomControls
    {
        public class BindableToolbarItem : ToolbarItem
        {
            public static readonly BindableProperty IsVisibleProperty =
                BindableProperty.Create("BindableToolbarItem", typeof (bool), typeof (ToolbarItem),
                    true, BindingMode.TwoWay, propertyChanged: OnIsVisibleChanged);
    
            public BindableToolbarItem()
            {
                InitVisibility();
            }
    
            public bool IsVisible
            {
                get { return (bool) GetValue(IsVisibleProperty); }
                set { SetValue(IsVisibleProperty, value); }
            }
    
            private void InitVisibility()
            {
                OnIsVisibleChanged(this, false, IsVisible);
            }
    
            private static void OnIsVisibleChanged(BindableObject bindable, object oldvalue, object newvalue)
            {
                var item = bindable as BindableToolbarItem;
    
                if (item != null && item.Parent == null)
                    return;
    
                if (item != null)
                {
                    var items = ((Page) item.Parent)?.ToolbarItems;
    
                    if (Equals(items, null)) return;
                    if ((bool)newvalue && !items.Contains(item))
                    {
                        Device.BeginInvokeOnMainThread(() => { items.Add(item); });
                    }
                    else if (!(bool)newvalue && items.Contains(item))
                    {
                        Device.BeginInvokeOnMainThread(() => { items.Remove(item); });
                    }
                }
            }
        }
    
  • BrentSaferBrentSafer USMember ✭✭

    Thanks @NMackay for those changes/fixes. They resolved some small cleanup issues I was having with old version.

Sign In or Register to comment.