在 DropDownButton 中组织项目?
Organize Items in DropDownButton?
我在 ObservableCollection
中有 collection 个项目,每个项目都有一个特定的国家名称(这只是一个字符串)。这是我的 collection:
private ObservableCollection<League> _leagues = new ObservableCollection<League>();
public ObservableCollection<League> Leagues
{
get
{
return _leagues;
}
set
{
_leagues = value;
OnPropertyChanged();
}
}
League
模型只有一个 Name
和一个 NationName
属性。
Xaml
看起来像这样:
<Controls:DropDownButton Content="Leagues" x:Name="LeagueMenu"
ItemsSource="{Binding Leagues}"
ItemTemplate="{StaticResource CombinedTemplate}" >
<Controls:DropDownButton.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding NationName}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</Controls:DropDownButton.GroupStyle>
</Controls:DropDownButton>
但我没有得到 NationName
属性 的任何 header,DropDown
中的项目没有 header 而是作为列表组织的,所以没有组织。
我正在尝试获取 this predisposition.
我做错了什么?
如果您查看链接到的其他 post,答案就是一切 - 特别是您需要绑定到 Collection 视图,而不是直接绑定到 collection。然后您可以在 Collection 视图上设置分组。
因此,在您的情况下,定义 属性:
public ICollectionView LeaguesView { get; private set; }
然后在您创建联赛后 Collection,将视图附加到您的 collection,然后在视图上设置分组:
LeaguesView = (ListCollectionView)CollectionViewSource.GetDefaultView(Leagues);
LeaguesView.GroupDesriptions.Add(new PropertyGroupDescription("NationName"));
然后,将您的 DropDownButton ItemSource 绑定到 LeaguesView,并将您的 HeaderTemplate 更改为绑定到 "Name" - 这是组的名称:
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
如果您想显示组中有多少项目,您也可以在其中使用 ItemCount 属性。
预赛
在 WPF(DropDownButton
的派生)中对 ItemsControl
中的项目进行分组非常简单,分两步完成。首先,您需要通过调整与源集合关联的 ICollectionView
来设置项目源。然后您需要使用至少一个 GroupStyle
项目填充 ItemsControl.GroupStyle
集合 - 否则这些项目将以普通(非分组)方式呈现。
诊断
您面临的主要问题是让下拉菜单以分组方式显示项目。不幸的是,与设置项目源不同,在 DropDownButton
控件的情况下,这不是一件容易完成的事情。其原因源于控件(或更准确地说,它的模板)的设计方式 - 下拉列表显示在 ContextMenu
内,附加到作为模板一部分的 Button
(参见 MahApps.Metro source code)。现在 ContextMenu
也派生自 ItemsControl
,并且其大部分属性都绑定到模板化 DropDownButton
的相应属性。然而,它的 GroupStyle
属性 并非如此,因为它是只读的非依赖项 属性,无法绑定或设置事件样式。这意味着即使您将项目添加到 DropDownButton.GroupStyle
集合,ContextMenu.GroupStyle
集合仍为空,因此项目以非分组方式显示。
解决方案(解决方法)
最可靠但最麻烦的解决方案是重新模板化控件并将 GroupStyle
项直接添加到 ContextMenu.GroupStyle
集合。但我可以为您提供更简洁的解决方法。
首先,让我们来处理第一步——设置项目源。最简单的方法(在我看来)是在 XAML 中使用 CollectionViewSource
。在你的情况下,它会归结为以下几行:
<mah:DropDownButton>
<mah:DropDownButton.Resources>
<CollectionViewSource x:Key="LeaguesViewSource" Source="{Binding Leagues}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="NationName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</mah:DropDownButton.Resources>
<mah:DropDownButton.ItemsSource>
<Binding Source="{StaticResource LeaguesViewSource}" />
</mah:DropDownButton.ItemsSource>
</mah:DropDownButton>
现在是主要部分 - 我们的想法是创建一个助手 class,它将包含一个附加的依赖项 属性,它将所有者 DropDownButton
控制权分配给ContextMenu
负责展示其项目。更改所有者后,我们将观察其 DropDownButton.GroupStyle
集合并使用 ContextMenu.GroupStyleSelector
向 ContextMenu
提供来自其所有者集合的项目。这是代码:
public static class DropDownButtonHelper
{
public static readonly DependencyProperty OwnerProperty =
DependencyProperty.RegisterAttached("Owner", typeof(DropDownButton), typeof(DropDownButtonHelper), new PropertyMetadata(OwnerChanged));
public static DropDownButton GetOwner(ContextMenu menu)
{
return (DropDownButton)menu.GetValue(OwnerProperty);
}
public static void SetOwner(ContextMenu menu, DropDownButton value)
{
menu.SetValue(OwnerProperty, value);
}
private static void OwnerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var menu = (ContextMenu)d;
if (e.OldValue != null)
//unsubscribe from the old owner
((DropDownButton)e.OldValue).GroupStyle.CollectionChanged -= menu.OwnerGroupStyleChanged;
if (e.NewValue != null)
{
var button = (DropDownButton)e.NewValue;
//subscribe to new owner
button.GroupStyle.CollectionChanged += menu.OwnerGroupStyleChanged;
menu.GroupStyleSelector = button.SelectGroupStyle;
}
else
menu.GroupStyleSelector = null;
}
private static void OwnerGroupStyleChanged(this ContextMenu menu, object sender, NotifyCollectionChangedEventArgs e)
{
//this method is invoked whenever owners GroupStyle collection is modified,
//so we need to update the GroupStyleSelector
menu.GroupStyleSelector = GetOwner(menu).SelectGroupStyle;
}
private static GroupStyle SelectGroupStyle(this DropDownButton button, CollectionViewGroup group, int level)
{
//we select a proper GroupStyle from the owner's GroupStyle collection
var index = Math.Min(level, button.GroupStyle.Count - 1);
return button.GroupStyle.Any() ? button.GroupStyle[index] : null;
}
}
为了完成第二步,我们需要为 ContextMenu
绑定 Owner
属性(我们将使用 DropDownButton.MenuStyle
来完成)并添加一些 GroupStyle
项目到 DropDownButton
:
<mah:DropDownButton>
<mah:DropDownButton.MenuStyle>
<Style TargetType="ContextMenu" BasedOn="{StaticResource {x:Type ContextMenu}}">
<Setter Property="local:DropDownButtonHelper.Owner" Value="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
</mah:DropDownButton.MenuStyle>
<mah:DropDownButton.GroupStyle>
<GroupStyle />
</mah:DropDownButton.GroupStyle>
</mah:DropDownButton>
我认为这应该足以实现您的目标。
我在 ObservableCollection
中有 collection 个项目,每个项目都有一个特定的国家名称(这只是一个字符串)。这是我的 collection:
private ObservableCollection<League> _leagues = new ObservableCollection<League>();
public ObservableCollection<League> Leagues
{
get
{
return _leagues;
}
set
{
_leagues = value;
OnPropertyChanged();
}
}
League
模型只有一个 Name
和一个 NationName
属性。
Xaml
看起来像这样:
<Controls:DropDownButton Content="Leagues" x:Name="LeagueMenu"
ItemsSource="{Binding Leagues}"
ItemTemplate="{StaticResource CombinedTemplate}" >
<Controls:DropDownButton.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding NationName}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</Controls:DropDownButton.GroupStyle>
</Controls:DropDownButton>
但我没有得到 NationName
属性 的任何 header,DropDown
中的项目没有 header 而是作为列表组织的,所以没有组织。
我正在尝试获取 this predisposition.
我做错了什么?
如果您查看链接到的其他 post,答案就是一切 - 特别是您需要绑定到 Collection 视图,而不是直接绑定到 collection。然后您可以在 Collection 视图上设置分组。
因此,在您的情况下,定义 属性:
public ICollectionView LeaguesView { get; private set; }
然后在您创建联赛后 Collection,将视图附加到您的 collection,然后在视图上设置分组:
LeaguesView = (ListCollectionView)CollectionViewSource.GetDefaultView(Leagues);
LeaguesView.GroupDesriptions.Add(new PropertyGroupDescription("NationName"));
然后,将您的 DropDownButton ItemSource 绑定到 LeaguesView,并将您的 HeaderTemplate 更改为绑定到 "Name" - 这是组的名称:
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
如果您想显示组中有多少项目,您也可以在其中使用 ItemCount 属性。
预赛
在 WPF(DropDownButton
的派生)中对 ItemsControl
中的项目进行分组非常简单,分两步完成。首先,您需要通过调整与源集合关联的 ICollectionView
来设置项目源。然后您需要使用至少一个 GroupStyle
项目填充 ItemsControl.GroupStyle
集合 - 否则这些项目将以普通(非分组)方式呈现。
诊断
您面临的主要问题是让下拉菜单以分组方式显示项目。不幸的是,与设置项目源不同,在 DropDownButton
控件的情况下,这不是一件容易完成的事情。其原因源于控件(或更准确地说,它的模板)的设计方式 - 下拉列表显示在 ContextMenu
内,附加到作为模板一部分的 Button
(参见 MahApps.Metro source code)。现在 ContextMenu
也派生自 ItemsControl
,并且其大部分属性都绑定到模板化 DropDownButton
的相应属性。然而,它的 GroupStyle
属性 并非如此,因为它是只读的非依赖项 属性,无法绑定或设置事件样式。这意味着即使您将项目添加到 DropDownButton.GroupStyle
集合,ContextMenu.GroupStyle
集合仍为空,因此项目以非分组方式显示。
解决方案(解决方法)
最可靠但最麻烦的解决方案是重新模板化控件并将 GroupStyle
项直接添加到 ContextMenu.GroupStyle
集合。但我可以为您提供更简洁的解决方法。
首先,让我们来处理第一步——设置项目源。最简单的方法(在我看来)是在 XAML 中使用 CollectionViewSource
。在你的情况下,它会归结为以下几行:
<mah:DropDownButton>
<mah:DropDownButton.Resources>
<CollectionViewSource x:Key="LeaguesViewSource" Source="{Binding Leagues}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="NationName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</mah:DropDownButton.Resources>
<mah:DropDownButton.ItemsSource>
<Binding Source="{StaticResource LeaguesViewSource}" />
</mah:DropDownButton.ItemsSource>
</mah:DropDownButton>
现在是主要部分 - 我们的想法是创建一个助手 class,它将包含一个附加的依赖项 属性,它将所有者 DropDownButton
控制权分配给ContextMenu
负责展示其项目。更改所有者后,我们将观察其 DropDownButton.GroupStyle
集合并使用 ContextMenu.GroupStyleSelector
向 ContextMenu
提供来自其所有者集合的项目。这是代码:
public static class DropDownButtonHelper
{
public static readonly DependencyProperty OwnerProperty =
DependencyProperty.RegisterAttached("Owner", typeof(DropDownButton), typeof(DropDownButtonHelper), new PropertyMetadata(OwnerChanged));
public static DropDownButton GetOwner(ContextMenu menu)
{
return (DropDownButton)menu.GetValue(OwnerProperty);
}
public static void SetOwner(ContextMenu menu, DropDownButton value)
{
menu.SetValue(OwnerProperty, value);
}
private static void OwnerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var menu = (ContextMenu)d;
if (e.OldValue != null)
//unsubscribe from the old owner
((DropDownButton)e.OldValue).GroupStyle.CollectionChanged -= menu.OwnerGroupStyleChanged;
if (e.NewValue != null)
{
var button = (DropDownButton)e.NewValue;
//subscribe to new owner
button.GroupStyle.CollectionChanged += menu.OwnerGroupStyleChanged;
menu.GroupStyleSelector = button.SelectGroupStyle;
}
else
menu.GroupStyleSelector = null;
}
private static void OwnerGroupStyleChanged(this ContextMenu menu, object sender, NotifyCollectionChangedEventArgs e)
{
//this method is invoked whenever owners GroupStyle collection is modified,
//so we need to update the GroupStyleSelector
menu.GroupStyleSelector = GetOwner(menu).SelectGroupStyle;
}
private static GroupStyle SelectGroupStyle(this DropDownButton button, CollectionViewGroup group, int level)
{
//we select a proper GroupStyle from the owner's GroupStyle collection
var index = Math.Min(level, button.GroupStyle.Count - 1);
return button.GroupStyle.Any() ? button.GroupStyle[index] : null;
}
}
为了完成第二步,我们需要为 ContextMenu
绑定 Owner
属性(我们将使用 DropDownButton.MenuStyle
来完成)并添加一些 GroupStyle
项目到 DropDownButton
:
<mah:DropDownButton>
<mah:DropDownButton.MenuStyle>
<Style TargetType="ContextMenu" BasedOn="{StaticResource {x:Type ContextMenu}}">
<Setter Property="local:DropDownButtonHelper.Owner" Value="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
</mah:DropDownButton.MenuStyle>
<mah:DropDownButton.GroupStyle>
<GroupStyle />
</mah:DropDownButton.GroupStyle>
</mah:DropDownButton>
我认为这应该足以实现您的目标。