I have come across lots of questions regarding this topic, however, I haven't found one that would solve my problem. For example, I can't use this solution since I'm using MVVM and I register my view models with the help of the host builder. This question sets a view model's property to a text block's text property with the help of this one. I have been trying to use the Binding Proxy but couldn't get it to work.
To be more specific, I use MVVM and so I don't have default constructors. I set my view's data context in my App.xaml like this:
<Application.Resources>
<ResourceDictionary>
..
<DataTemplate DataType="{x:Type viewmodels:HomeViewModel}">
<views:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:LogoutViewModel}">
<views:LogoutView />
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
My user control on which I have the hamburger menu looks like this:
HamburgerMenuRipple.xaml
<mah:HamburgerMenu
x:Name="HamburgerMenuControl"
DisplayMode="CompactInline"
HamburgerButtonClick="HamburgerMenuControl_HamburgerButtonClick"
IsPaneOpen="True"
ItemInvoked="HamburgerMenuControl_OnItemInvoked"
ItemTemplate="{StaticResource MenuItemTemplate}"
OpenPaneLength="275"
OptionsItemTemplate="{StaticResource MenuItemTemplate}"
SelectedIndex="0"
Style="{StaticResource MahApps.Styles.HamburgerMenu.Ripple}"
VerticalScrollBarOnLeftSide="False">
<!-- Header -->
<mah:HamburgerMenu.HamburgerMenuHeaderTemplate>
<DataTemplate>
<TextBlock
Padding="10,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="16"
FontWeight="DemiBold"
Foreground="White"
TextWrapping="Wrap" />
</DataTemplate>
</mah:HamburgerMenu.HamburgerMenuHeaderTemplate>
<!-- Items -->
<mah:HamburgerMenu.ItemsSource>
<mah:HamburgerMenuItemCollection>
<mah:HamburgerMenuIconItem Icon="{iconPacks:Material Kind=Home}" Label="Home" />
<mah:HamburgerMenuIconItem Icon="{iconPacks:Material Kind=Bell}" Label="Notifications" />
..
</mah:HamburgerMenuItemCollection>
</mah:HamburgerMenu.ItemsSource>
<!-- Options -->
<mah:HamburgerMenu.OptionsItemsSource>
<mah:HamburgerMenuItemCollection>
<mah:HamburgerMenuSeparatorItem x:Name="Separator_2" IsVisible="{Binding ElementName=Separator_2}" />
<mah:HamburgerMenuIconItem Icon="{iconPacks:Material Kind=Cog}" Label="Settings" />
<mah:HamburgerMenuIconItem Icon="{iconPacks:Material Kind=Logout}" Label="Logout">
<mah:HamburgerMenuIconItem.Tag>
<views:LogoutView/> <!--How can I set the datacontext here?-->
</mah:HamburgerMenuIconItem.Tag>
</mah:HamburgerMenuIconItem>
</mah:HamburgerMenuItemCollection>
</mah:HamburgerMenu.OptionsItemsSource>
<mah:HamburgerMenu.ContentTemplate>
<DataTemplate DataType="{x:Type mah:HamburgerMenuIconItem}">
<Grid Margin="20,0,10,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Margin="0,15,0,5"
Padding="0"
FontFamily="{DynamicResource MahApps.Fonts.Family.Header}"
FontSize="16"
Text="{Binding Label}" />
<ScrollViewer
Grid.Row="1"
Focusable="False"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<ContentControl Content="{Binding Tag}" Focusable="False" />
</ScrollViewer>
</Grid>
</DataTemplate>
</mah:HamburgerMenu.ContentTemplate>
</mah:HamburgerMenu>
My Logout view is simple for now, but it looks like this:
LogoutView.xaml
<StackPanel Orientation="Vertical">
<TextBlock Text="Do you want to log out?" />
<Button Command="{Binding LogoutCommand}" Content="Log out" />
</StackPanel>
And the view model:
LogoutViewModel.cs
public class LogoutViewModel : ViewModelBase
{
public ICommand LogoutCommand { get; }
public LogoutViewModel(IAuthenticationService authenticationService, INavigationService loginNavigationService)
{
LogoutCommand = new LogoutCommand(this, authenticationService, loginNavigationService);
}
}
AddViewModelsHostBuilderExtensions.cs
public static IHostBuilder AddViewModels(this IHostBuilder host)
{
host.ConfigureServices(services =>
{
services.AddTransient<MainViewModel>();
services.AddSingleton<CreateViewModel<LoginViewModel>>(services => () => CreateLoginViewModel(services));
services.AddSingleton<CreateViewModel<HomeViewModel>>(services => () => CreateHomeViewModel(services));
services.AddSingleton<CreateViewModel<LogoutViewModel>>(services => () => CreateLogoutViewModel(services));
});
return host;
}
private static LoginViewModel CreateLoginViewModel(IServiceProvider services)
{
return new LoginViewModel(
services.GetRequiredService<NavigationService<HomeViewModel>>());
}
private static HomeViewModel CreateHomeViewModel(IServiceProvider services)
{
return new HomeViewModel();
}
private static LogoutViewModel CreateLogoutViewModel(IServiceProvider services)
{
return new LogoutViewModel(
services.GetRequiredService<NavigationService<LoginViewModel>>());
}
My view appears correctly when I click the 'Log out' item, however, whenever I click on the 'Log out' button, the command doesn't fire. Is it because the view doesn't have the view model's data context (according to the other questions)? Or did I make a mistake somewhere else? How do I set the data context of the view in this situation?
CodePudding user response:
Fortunately, @mm8 's comment helped me to start working in the right direction, and then I could figure out the rest by myself, so I post my solution here, hope it might help others in the future.
Basically, what I did was that I created a view model property in my HomeViewModel. I guess you can create it in this property, but I register view models in my host builder so I get it from my constructor.
HomeViewModel.cs
public class HomeViewModel : ViewModelBase
{
public LogoutViewModel LogoutViewModel { get; }
public HomeViewModel(LogoutViewModel logoutViewModel)
{
LogoutViewModel = logoutViewModel;
}
}
After this, I created a dependency property in my hamburger menu user control so that I can pass the view model from the HomeViewModel to the hamburger menu user control.
HomeView.xaml
..
<controls:HamburgerMenuRipple LogoutViewDataContext="{Binding LogoutViewModel}" />
..
HamburgerMenuRipple.xaml.cs
public static readonly DependencyProperty LogoutViewDataContextProperty =
DependencyProperty.Register(nameof(LogoutViewDataContext), typeof(LogoutViewModel), typeof(HamburgerMenuRipple), new PropertyMetadata(null));
public LogoutViewModel LogoutViewDataContext
{
get { return (LogoutViewModel)GetValue(LogoutViewDataContextProperty); }
set { SetValue(LogoutViewDataContextProperty, value); }
}
And then I set the data context of the tag's user control with the help of the OnItemInvoked command.
private void HamburgerMenuControl_OnItemInvoked(object sender, HamburgerMenuItemInvokedEventArgs e)
{
HamburgerMenuControl.Content = e.InvokedItem;
var iconItemTag = (e.InvokedItem as HamburgerMenuIconItem).Tag;
if (iconItemTag != null)
{
switch(iconItemTag)
{
case LogoutView:
ErrorMessage = string.Empty;
(iconItemTag as UserControl).DataContext = LogoutViewDataContext;
break;
default:
ErrorMessage = "No data context has been bound to this view.";
break;
}
}
}
HamburgerMenuRipple.xaml
<mah:HamburgerMenu
x:Name="HamburgerMenuControl"
DisplayMode="CompactInline"
IsPaneOpen="True"
ItemInvoked="HamburgerMenuControl_OnItemInvoked"
..
<mah:HamburgerMenu.OptionsItemsSource>
<mah:HamburgerMenuItemCollection>
<mah:HamburgerMenuSeparatorItem />
<mah:HamburgerMenuIconItem
x:Name="LogoutIconItem"
Icon="{iconPacks:Material Kind=Logout}"
Label="Logout">
<mah:HamburgerMenuIconItem.Tag>
<views:LogoutView HorizontalAlignment="Center" VerticalAlignment="Center" />
</mah:HamburgerMenuIconItem.Tag>
</mah:HamburgerMenuIconItem>
</mah:HamburgerMenuItemCollection>
</mah:HamburgerMenu.OptionsItemsSource>
..