Home > other >  Set the data context of a custom control of mahapps hamburger menu tag
Set the data context of a custom control of mahapps hamburger menu tag

Time:04-13

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>

..
  • Related