Steve0212
Steve0212

Reputation: 712

Start UWP App with Hamburger Menu collapsed - Template10

I am new to UWP and am using Template 10. I have done a lot of WPF though. I would like the app to start with the hamburger menu open or closed based on how the user left it when they last used the app. To that end, I have made a preference and I bound IsOpen to a property in my settings view model.

However, it does not work. When starting the application, the property in the view model is always set to true from the control. I added a button to change the view model property (which would do a raisepropertychanged) and it did not toggle the menu. However, toggling the menu the from the UI with the mouse will call the setter in my view model.

Here is what I did:

  1. I created a new project using the Hamburger template.

  2. I added a new property to SettingsService. It mirrors the format of the other properties in Settings Service

    public bool HamburgerIsOpen
    {
        get { return _helper.Read<bool>(nameof(HamburgerIsOpen), true); }
        set
        {
            _helper.Write(nameof(HamburgerIsOpen), value);
        }
    }
    
  3. I added a new property to SettingsPartViewModel. It mirrors the format of the other properties in SettingsPartViewModel

    public bool HamburgerIsOpen
    {
        get { return _settings.HamburgerIsOpen; }
        set { _settings.HamburgerIsOpen = value; base.RaisePropertyChanged(); }
    }
    
  4. I updated Shell.xaml adding a datacontext and the binding

<Page.DataContext>
     <viewModels:SettingsPageViewModel x:Name="ViewModel" />
</Page.DataContext>

<Controls:HamburgerMenu x:Name="MyHamburgerMenu" IsOpen="{x:Bind ViewModel.SettingsPartViewModel.HamburgerIsOpen, Mode=TwoWay}">

The control is definitely setting IsOpen to true on start even though it did a get first that returned false.

Any ideas how I can do this?

Thanks.

Upvotes: 0

Views: 537

Answers (2)

Steve0212
Steve0212

Reputation: 712

OK - here is what I ended up doing. It seems simpler to me than keeping track of loading, etc. This still feels like a bug to me in the Template 10, but this will work around it. Long story short, I don't do the binding in XAML, I do it after the page is loaded

Shell.xaml -> create an event handler for Loaded on the page, no binding on IsOpen and set the data context.

Loaded="Shell_OnLoaded"
...
<Page.DataContext>
    <viewModels:SettingsPageViewModel x:Name="ViewModel" />
</Page.DataContext>

<Controls:HamburgerMenu x:Name="MyHamburgerMenu">

Shell.xaml.cs -> here is the event handler for IsLoaded. It sets the binding AFTER the page is loaded. This stops the Hamburger control from setting the setting to true on load.

        private void Shell_OnLoaded(object sender, RoutedEventArgs e)
        {
           // now that the hamburger control is loaded, bind it for the future opening and closing
           // if you bind it in XAML, the control always passes the value of true on load to the binding
           // so the control always starts open
           Binding myBinding = new Binding
           {
               Path = new PropertyPath("HamburgerIsOpen"),
               Source = Services.SettingsServices.SettingsService.Instance,
               Mode = BindingMode.TwoWay
           };
           MyHamburgerMenu.SetBinding(HamburgerMenu.IsOpenProperty, myBinding);
        }

SettingsPageViewModel.cs -> define the property

        public bool HamburgerIsOpen
        {
            get { return _settings.HamburgerIsOpen; }
            set { _settings.HamburgerIsOpen = value; base.RaisePropertyChanged(); }
        }

SettingsService.cs -> define the save mechanism. No need to call the view like other properties in SettingsService do...

        public bool HamburgerIsOpen
        {
            get { return _helper.Read<bool>(nameof(HamburgerIsOpen), true); }
            set
            {
                _helper.Write(nameof(HamburgerIsOpen), value);
            }
        }

Upvotes: -1

iam.Carrot
iam.Carrot

Reputation: 5286

The reason why your hamburger is set to open is because it's IsOpen property has a fallback value to True. And probably the {Binding} is failing as it cannot get access to that or it just can't find it. Because you're using UWP and Template10. I would advise:

  1. Use {x:Bind} instead of {Binding} because this ways you'll know if the compiler can find your provided binding reference.
  2. Create the a property in the viewModel of Shell.xaml (assuming you're using the Hamburger Template) or if not? then create a property in the viewModel of the View in which the HamburgerControl is being used.
  3. Now in that property, you fetch your data from the SettingsPageViewModel.SettingsPartViewModel.HamburgerIsOpen in the Get and update the new value to the same in the set. This ways you have the required data in the viewModel of the view where the hamburger is being used.
  4. Now x:Bind the IsOpen property to the Property in a Mode=TwoWay. Now you'll have a fully functional hamburger menu with resuming states. Note: if you're x:Binding your property then make sure you add the ViewModel. prefix to your binding and name the <DataContext> as ViewModel.

Sorry I had to remove all the code, maybe because i'll put up the code here and the older one is no longer relevant or can be found from the edits.

Coming to the point, I have solved the issue, there seems to be some calling service that calls the Hamburger control and sets the value to falsewhen the view hasn't loaded just yet. I've come up with a rather cheeky way to get a quite neat work around, some might say it's not ideal but it's a fix.

The Code:

In your Shell.xmal global create a bool property eg: private bool loaded = false;. This is a property that helps us to know that has the view been loaded, since all the calls to the setter were while the application hasn't loaded it seemed okay to by pass it and just allow the getter to access it.

Now In your Shell.xaml constructor subscribe to the shell.Loaded event,

eg: this.Loaded += Shell_Loaded; //in the shell.xmal.cs constructor

and the Shell_Loaded event:

private void Shell_Loaded(object sender, RoutedEventArgs e)=>
        loaded = true;

Note the method syntax is the one used in c# 6.0 for lower versions kindly use the traditional {}.

Also in the shell.xaml.cs add the bool property to handle the Hamburger menu open/closing

public bool IsHamOpen
    {
        get
        {
            return (new ViewModels.SettingsPageViewModel()).SettingsPartViewModel.IsHamOpen;
        }
        set
        {
            if (!value)
            {
                if (loaded)
                {
                    (new ViewModels.SettingsPageViewModel()).SettingsPartViewModel.IsHamOpen = value;
                }
                else
                    (new ViewModels.SettingsPageViewModel()).SettingsPartViewModel.IsHamOpen = true;
            }
            else
                (new ViewModels.SettingsPageViewModel()).SettingsPartViewModel.IsHamOpen = value;
        }
    }

please note: I am creating an instance of the ViewModel so that all the settings are changed via the settingsViewModel. In case some setting starts acting funny, we'll know where to find em.


Your Shell.xaml

In your Hamburger Control, link it to the code behind property,

<Controls:HamburgerMenu x:Name="MyHamburgerMenu" IsOpen="{x:Bind IsHamOpen,Mode=TwoWay}">

In the SettingsPageViewModel:

Create a property to handle the changes done to the hamburger menu. This ways you can also provide a simple toggle switch for the user to select his hamburger open or close by default.

      public bool IsHamOpen
      {
        get { return _settings.IsHamOpen; }
        set
        {
            _settings.IsHamOpen = value;
            base.RaisePropertyChanged();

            //the below is to hide and show the hamburger button. 
            //If the it's open then hide button 
            //else show the button to open it.
            //this is just as per your requiremnts. 
            //I just wanted to put it in
            ShowHamburgerButton = !value;


        }
    }

In your Settings Service:

Add a property to read and write the settings and apply them to the hamburger control:

 public bool IsHamOpen
    {
        get { return _helper.Read<bool>(nameof(IsHamOpen), false); }
        set
        {
            _helper.Write(nameof(IsHamOpen), value);
            Views.Shell.HamburgerMenu.IsOpen = value;
        }
    }

Finally The Settings View

This is completely optional. if you want the user to be able to set his preference about the hamburger control then give a neat toggleSwitch and x:Bind it to the ViewModel.

<ToggleSwitch x:Name="OpenHamWhenStart"
                                  Header="Default Hamburger Open or Close"
                                  IsOn="{Binding IsHamOpen, Mode=TwoWay}"
                                  OffContent="Hamburger Menu is Closed"
                                  OnContent="Hamburger Menu is Open"
                                  RelativePanel.AlignLeftWithPanel="True"
                                  RelativePanel.Below="BusyTextTextBox" />

And there you have it. Please let me know if there is any issue. you can reach me on the comments section

Upvotes: 1

Related Questions