Search code examples
c#wpfxamldata-bindingcaliburn.micro

How do I pass a property of Datatemplate's Datatype to Caliburn.Micros Message.Attach?


I'm currently in the process of implementing a menu navigation system for a group project. We're using Caliburn.Micro and the MVVM pattern.

The menu is located in the ShellView.xaml and is based on a List<NavigationMenuItem> that has been filtered using the Role property.

public class NavigationMenuItem
    {
        public IScreen ViewModel { get; set; }
        public string IconPath { get; set; }

        public string Role { get; set; }

        public NavigationMenuItem(IScreen viewModel, string iconPath, string role)
        {
            ViewModel = viewModel;
            IconPath = iconPath;
            Role = role;
        }
    }

The menu is displaying just fine, but now I want to pass the ViewModel-property to a method in the ShellViewModel.cs, such that I can activate a view change. This is how the ShellView.xaml looks so far:

<Window
    x:Class="P3GG.Views.ShellView"
    xmlns:cal="http://www.caliburnproject.org"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:P3GG.Views"
    xmlns:models="clr-namespace:P3GG.Models"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:templates="clr-namespace:P3GG.Templates"
     xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
    Title="Title"
    Width="800"
    Height="600"
    WindowStartupLocation="CenterScreen"
    mc:Ignorable="d">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <ListBox Visibility="{Binding SidebarIsVisible, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}"
                 Grid.Column="0"
                 Background="#333333"
                 x:Name="NavigationMenu">
            <ListBox.ItemTemplate>
                <DataTemplate DataType="models:NavigationMenuItem">
                    <Button cal:Message.Attach="Handle( <!-- pass content of NavigationMenuItem.ViewModel --> )" Style="{StaticResource BtnSidebar}">
                        <Image Margin="20" Source="{Binding IconPath}"/>
                    </Button>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ContentControl  Grid.Column="1" x:Name="ActiveItem"/>
    </Grid>
</Window>

The ShellViewModel.cs currently contains two Handle methods:

public void Handle(Screen screen)
public void Handle(User user)

How do I pass the ViewModel property of a given NavigationMenu to the Handle(Screen) method?

EDIT Added Handle method per request

public void Handle(Screen screen)
        {
            if (screen is LoginViewModel)
            {
                SidebarIsVisible = false;
                NotifyOfPropertyChange(() => SidebarIsVisible);
            }
            else
            {
                SidebarIsVisible = true;
                NotifyOfPropertyChange(() => SidebarIsVisible);
            }
            ActivateItem(screen);
        }

Solution

  • Maybe you can try something like this, as explained here:

    <Button cal:Message.Attach="[Event Click] = [Action Handle($dataContext)]" Style="{StaticResource BtnSidebar}">
        <Image Margin="20" Source="{Binding IconPath}"/>
    </Button>
    

    This will pass the full view model (DataContext) to your handler.

    To pass a specific property of your view model, use the long syntax as indicated here:

    <Button Style="{StaticResource BtnSidebar}">
        <i:Interaction.Triggers> 
            <i:EventTrigger EventName="Click"> 
                <cal:ActionMessage MethodName="Handle"> 
                   <cal:Parameter Value="{Binding ViewModel}" /> 
                </cal:ActionMessage> 
            </i:EventTrigger> 
        </i:Interaction.Triggers> 
        <Image Margin="20" Source="{Binding IconPath}"/>
    </Button>
    

    with i defined as

    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    

    You could also change the DataContext property of the Button to the property of interest, like:

    <Button DataContext="{Binding ViewModel}" cal:Message.Attach="[Event Click] = [Action Handle($dataContext)]" Style="{StaticResource BtnSidebar}">
        <Image Margin="20" Source="{Binding IconPath}"/>
    </Button>
    

    so only the correct property gets passed to your hendler, but this feels like a hack. On click, this will hand-over the DataContext of the Button as parameter of your Handle method, which should be a NavigationMenuItem.