Try using a HierarchicalDataTemplate with an internal ContentPresenter instead. Take a look at this SO answer for more details.
views:
330answers:
4The "weird offset" is a MenuItem
. The parent MenuItem
is already generating a child MenuItem
for you, but your DataTemplate
adds a second one. Try this:
<MenuItem Header="File}">
<MenuItem Header="Preferences..." Command="{Binding ShowOptionsViewCommand}" />
<Separator />
<ItemsControl ItemsSource="{Binding RecentFiles}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayPath}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.OpenRecentFileCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
<Separator />
<MenuItem Header="Exit" Command="{Binding CloseCommand}" />
</MenuItem>
Note the simplified DataTemplate
that just contains a TextBlock, and the ItemContainerStyle
to set properties on the generated MenuItem
.
HTH, Kent
I tried using a CompositeCollection
as suggested by Kent Boogaart, but I could make it work because of a bug in wpf not allowing to use a RelativeSource binding in a CollectionContainer
.
The solution I used is to have the RecentFiles
in its own sub menu bound to the Collection via the ItemsSource
property.
I really wanted to have the list in the 'File' menu but I guess this is the next best thing...
Edit
Inspired by this article I built a custom and more general MenuItemList
:
public class MenuItemList : Separator {
#region Private Members
private MenuItem m_Parent;
private List<MenuItem> m_InsertedMenuItems;
#endregion
public MenuItemList() {
Loaded += (s, e) => HookFileMenu();
}
private void HookFileMenu() {
m_Parent = Parent as MenuItem;
if (m_Parent == null) {
throw new InvalidOperationException("Parent must be a MenuItem");
}
if (ParentMenuItem == m_Parent) {
return;
}
if (ParentMenuItem != null) {
ParentMenuItem.SubmenuOpened -= _FileMenu_SubmenuOpened;
}
ParentMenuItem = m_Parent;
ParentMenuItem.SubmenuOpened += _FileMenu_SubmenuOpened;
}
private void _FileMenu_SubmenuOpened(object sender, RoutedEventArgs e) {
DataBind();
}
#region Properties
public MenuItem ParentMenuItem { get; private set; }
#region ItemsSource
/// <summary>
/// ItemsSource Dependency Property
/// </summary>
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(MenuItemList),
new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(OnItemsSourceChanged)));
/// <summary>
/// Gets or sets a collection used to generate the content of the <see cref="MenuItemList"/>. This is a dependency property.
/// </summary>
public IEnumerable ItemsSource {
get { return (IEnumerable) GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
/// <summary>
/// Handles changes to the ItemsSource property.
/// </summary>
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
((MenuItemList) d).OnItemsSourceChanged(e);
}
/// <summary>
/// Provides derived classes an opportunity to handle changes to the ItemsSource property.
/// </summary>
protected virtual void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e) {
DataBind();
}
#endregion
#region ItemContainerStyle
/// <summary>
/// ItemsContainerStyle Dependency Property
/// </summary>
public static readonly DependencyProperty ItemContainerStyleProperty =
DependencyProperty.Register("ItemContainerStyle", typeof(Style), typeof(MenuItemList),
new FrameworkPropertyMetadata((Style) null));
/// <summary>
/// Gets or sets the <see cref="System.Windows.Style"/> that is applied to the container element generated for each item. This is a dependency property.
/// </summary>
public Style ItemContainerStyle {
get { return (Style) GetValue(ItemContainerStyleProperty); }
set { SetValue(ItemContainerStyleProperty, value); }
}
#endregion
#endregion
private void DataBind() {
RemoveMenuItems();
InsertMenuItems();
}
private void RemoveMenuItems() {
if (m_InsertedMenuItems != null) {
foreach (var menuItem in m_InsertedMenuItems) {
ParentMenuItem.Items.Remove(menuItem);
}
}
}
private void InsertMenuItems() {
if (ItemsSource == null) {
return;
}
if (ParentMenuItem != null) {
m_InsertedMenuItems = new List<MenuItem>();
int iMenuItem = ParentMenuItem.Items.IndexOf(this);
foreach (var item in ItemsSource) {
var menuItem = new MenuItem();
menuItem.DataContext = item;
menuItem.Style = ItemContainerStyle;
ParentMenuItem.Items.Insert(++iMenuItem, menuItem);
m_InsertedMenuItems.Add(menuItem);
}
}
}
}
It's far from perfect but it works for me. Feel free to comment on it...
This blog article shows how to make data-driven menus:
http://weblogs.asp.net/okloeten/archive/2007/11/14/5149692.aspx