Reputation: 712
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:
I created a new project using the Hamburger template.
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);
}
}
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(); }
}
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
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
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:
{x:Bind}
instead of {Binding}
because this ways you'll know if the compiler can find your provided binding reference. 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. 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.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:Bind
ing 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 false
when 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.
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