用Caliburn Micro进行MahApps.Metro HamburgerMenu

时间:2017-08-25 09:52:45

标签: c# wpf xaml caliburn.micro mahapps.metro

我在使用Caliburn Micro Conductor<>.Collection.OneActive和MahApps.Metro HamburgerMenu时遇到一些问题。来自few samples,但没有一个符合我的情况。

我的所有代码均可在this Github repository中找到。

我想在HamburgerMenu中显示一组窗格。每个窗格都有一个标题和显示名称:

public interface IPane : IHaveDisplayName, IActivate, IDeactivate
{
    PackIconModernKind Icon { get; }
}

就我而言,IPane是使用PaneViewModel实现的:

public class PaneViewModel : Screen, IPane
{
    public PaneViewModel(string displayName, PackIconModernKind icon)
    {
        this.Icon = icon;
        this.DisplayName = displayName;
    }

    public PackIconModernKind Icon { get; }
}

这有以下观点:

<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>

我的shell视图模型也很简单。它继承自Conductor<IPane>.Collection.OneActive,并接收它添加到Items集合的窗格列表:

public class ShellViewModel : Conductor<IPane>.Collection.OneActive
{
    public ShellViewModel(IEnumerable<IPane> pages)
    {
        this.DisplayName = "Shell!";

        this.Items.AddRange(pages);
    }
}

现在,这对我来说非常模糊。这是摘自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>

为了完成这项工作,我依靠两个转换器(他们坦率地做了比他们应该做的更多)。一个转换器使用ICollection<IPane>并使用HamburgerMenuItemCollection创建HamburgerMenuIconItem,其中Tag现在包含使用视图模型和菜单项的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(); } } 属性的双向链接

Tag

只要SelectedItem更改,第二个转换器就会使用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; } } 在视图模型和菜单项之间进行转换:

ShellViewModel

当我运行此代码并单击汉堡菜单中的项目时,页面每次都会切换。问题是,当应用程序首次运行时,没有选定的窗格,并且您无法使用OnViewAttached中可用的任何激活替换设置(例如OnActivateTag,或者事件构造函数),因为挂钩ShellViewModel的转换器代码尚未运行。

我对工作解决方案的要求:

  1. Caliburn的指挥必须负责,因为堆栈中的视图和视图模型依赖于要运行的激活逻辑。
  2. 应该可以在激活{% block body %} <div class="container"> <h3>List of products</h3> <table class="table table-striped"> <thead> <tr> <th>Product</th> <th>Description</th> <th>Size</th> <th>Charges</th> <th>Price</th> <th>Actions</th> <th>Desactivation</th> </tr> </thead> <tbody> {% for catalogue in catalogues %} <tr class="table"> <td>{{ catalogue.product}} </td> <td>{{ catalogue.description }} </td> <td>{{ catalogue.size}} </td> <td>{{ catalogue.charge }} </td> <td>{{ catalogue.price }}</td> <td> <a href="{{ path('catalogue_list_edit', { 'id': catalogue.id }) }}"><span class="glyphicon glyphicon-edit"></span></a> <a href="{{ path('catalogue_list_delete', { 'id': catalogue.id }) }}"><span class="glyphicon glyphicon-remove"></span></a> </td> <td><input type="checkbox" name="boutton35" value="Desactivation" /> </td> </tr> {% else %} {% endfor %} </tbody> </table> {% endblock %}
  3. 期间的某个时刻激活Caliburn中的第一个项目
  4. 应尊重关注点的分离,即视图模型不应该知道视图中正在使用汉堡包菜单。
  5. 请参阅the GitHub repository以获取可立即运行的解决方案。

1 个答案:

答案 0 :(得分:0)

我认为问题是由控件中的HamburgerMenu_Loaded方法引起的。如果在加载控件之前存在选定的项目,则替换汉堡包菜单的内容:

private void HamburgerMenu_Loaded(object sender, RoutedEventArgs e)
{
    var selectedItem = this._buttonsListView?.SelectedItem ?? this._optionsListView?.SelectedItem;
    if (selectedItem != null)
    {
        this.SetCurrentValue(ContentProperty, selectedItem);
    }
}

在您的情况下,ContentControl被删除,Conductor无法正常工作。

我试图通过将代码更改为以下内容来确定是否可以直接在MahApps中更改此行为:

if (this.Content != null)
{
    var selectedItem = this._buttonsListView?.SelectedItem ?? this._optionsListView?.SelectedItem;
    if (selectedItem != null)
    {
        this.SetCurrentValue(ContentProperty, selectedItem);
    }
}