I am having a few issues with using Caliburn Micro's Conductor<>.Collection.OneActive with MahApps.Metro HamburgerMenu. From a few samples, but none of them address my scenario.
All of my code is available in this Github repository.
I want to show a set of panes inside a HamburgerMenu. Each pane has a title and a display name:
public interface IPane : IHaveDisplayName, IActivate, IDeactivate
{
PackIconModernKind Icon { get; }
}
In my case, IPane is implemented using PaneViewModel:
public class PaneViewModel : Screen, IPane
{
public PaneViewModel(string displayName, PackIconModernKind icon)
{
this.Icon = icon;
this.DisplayName = displayName;
}
public PackIconModernKind Icon { get; }
}
This has the following view:
<UserControl x:Class="CaliburnMetroHamburgerMenu.Views.PaneView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Padding="12"
Background="Pink">
<StackPanel Orientation="Vertical">
<TextBlock Text="Non-bound text" />
<TextBlock x:Name="DisplayName" FontWeight="Bold" />
</StackPanel>
</UserControl>
My shell view model is also quite simple. It inherits from Conductor<IPane>.Collection.OneActive, and takes in a list of panes that it adds to its Items collection:
public class ShellViewModel : Conductor<IPane>.Collection.OneActive
{
public ShellViewModel(IEnumerable<IPane> pages)
{
this.DisplayName = "Shell!";
this.Items.AddRange(pages);
}
}
Now, this is very it gets fuzzy for me. This is an excerpt from ShellView.xaml:
<controls:HamburgerMenu
ItemsSource="{Binding Items, Converter={StaticResource PaneListToHamburgerMenuItemCollection}}"
SelectedItem="{Binding ActiveItem, Mode=TwoWay, Converter={StaticResource HamburgerMenuItemToPane}}">
<ContentControl cal:View.Model="{Binding ActiveItem}" />
<controls:HamburgerMenu.ItemTemplate>
<DataTemplate>
<Grid x:Name="RootGrid"
Height="48"
Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="48" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<iconPacks:PackIconModern
Grid.Column="0"
Kind="{Binding Icon}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="White" />
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
FontSize="16"
Foreground="White"
Text="{Binding Label}" />
</Grid>
</DataTemplate>
</controls:HamburgerMenu.ItemTemplate>
</controls:HamburgerMenu>
To make this work, I rely on two converters (who quite frankly do more than they should have to). One converter takes a ICollection<IPane> and creates a HamburgerMenuItemCollection with HamburgerMenuIconItems that are now contain a two-way link using the Tag properties of both the view model and the menu item.
class PaneListToHamburgerMenuItemCollection : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var viewModels = value as ICollection<IPane>;
var collection = new HamburgerMenuItemCollection();
foreach (var vm in viewModels)
{
var item = new HamburgerMenuIconItem();
item.Label = vm.DisplayName;
item.Icon = vm.Icon;
item.Tag = vm;
vm.Tag = item;
collection.Add(item);
}
return collection;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The second converter converts between the view model and the menu item using this Tag whenever the SelectedItem changes:
class HamburgerMenuItemToPane : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((IPane)value)?.Tag;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((HamburgerMenuIconItem)value)?.Tag;
}
}
When I run this code, and click the items in the hamburger menu, the page switches every time. The issue is that when the app first runs, there is no selected pane, and you cannot set one using any of the activation overrides available in ShellViewModel (such as OnViewAttached or OnActivate, or event the constructor), as the converter code that hooks up the Tag hasn't run yet.
My requirements for a working solution:
ShellViewModelPlease see the GitHub repository for a solution that should run straight away.
I believe the issue is caused by the HamburgerMenu_Loaded method inside the control. If there is a selected item before the control loads, the content of the hamburger menu is replaced:
private void HamburgerMenu_Loaded(object sender, RoutedEventArgs e)
{
var selectedItem = this._buttonsListView?.SelectedItem ?? this._optionsListView?.SelectedItem;
if (selectedItem != null)
{
this.SetCurrentValue(ContentProperty, selectedItem);
}
}
In your case, the ContentControl is removed and your Conductor cannot do its job.
I'm trying to see if this behavior can be changed in MahApps directly, by changing the code to something like this:
if (this.Content != null)
{
var selectedItem = this._buttonsListView?.SelectedItem ?? this._optionsListView?.SelectedItem;
if (selectedItem != null)
{
this.SetCurrentValue(ContentProperty, selectedItem);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With