2009-09-09 15 views
6

Tengo un 'Archivo' MenuItem donde me gustaría mostrar una lista de los archivos abiertos recientemente.¿Cómo puedo incorporar una lista de elementos de menú enlazados a otro elemento MenuItem en WPF?

Aquí es el XAML que tengo ahora:

<MenuItem Header="File}"> 
    <MenuItem Header="Preferences..." Command="{Binding ShowOptionsViewCommand}" /> 
    <Separator /> 
    <ItemsControl ItemsSource="{Binding RecentFiles}"> 
    <ItemsControl.ItemTemplate> 
     <DataTemplate> 
     <MenuItem Header="{Binding DisplayPath}" CommandParameter="{Binding}" 
      Command="{Binding Path=DataContext.OpenRecentFileCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"> 
     </MenuItem> 
     </DataTemplate> 
    </ItemsControl.ItemTemplate> 
    </ItemsControl> 
    <Separator /> 
    <MenuItem Header="Exit" Command="{Binding CloseCommand}" /> 
</MenuItem> 

Sin embargo, cuando se utiliza este código, hay una extraña desviación alrededor de las MenuItem s y parece que hay un recipiente alrededor de ellos. ¿Cómo puedo deshacerme de eso?

Aquí está una captura de pantalla de lo que parece:

alt text http://www.cote-soleil.be/FileMenu.png

+0

Este artículo de blog muestra cómo hacer menús basados ​​en datos: http://weblogs.asp.net/okloeten/archive/2007/11/14/5149692.aspx –

Respuesta

4

He intentado utilizar un CompositeCollection como se sugiere por Kent Boogaart, pero no pude hacerlo funcionar debido a un bug in wpf no permitiendo utilizar un RelativeSource vinculante en un CollectionContainer.

La solución utilicé es tener la RecentFiles en su propio submenú con destino a la colección a través de la propiedad ItemsSource.

Realmente quería tener la lista en el menú 'Archivo', pero supongo que esta es la segunda mejor opción ...

Editar

Inspirado por this article he construido una costumbre y más 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); 
     } 
    } 
    } 

} 

está lejos de ser perfecto, pero funciona para mí. Siéntase libre de comentar al respecto ...

1

intentar usar un HierarchicalDataTemplate con un ContentPresenter interna en lugar. Take a look at this SO answer for more details.

+0

The SO answer you linked es realmente para un submenú. Lo que me gustaría es que la lista dinámica forme parte de un menú no dinámico ... ¿Tienes alguna otra idea? –

+0

¿Entonces, básicamente, desea fusionar algunos elementos de menú dinámicos en un menú estáticamente definido? AFAIK, cuando haces un enlace de datos, puedes vincular un ItemsSource completo o compilas los contenidos manualmente (ya sea en XAML o desde código). Lo que haría sería hacer que todo el menú se vincule a un MenuItemViewModel, pero luego asegúrese de que a través de la API solo se puede editar la lista dinámica. –

6

El "desplazamiento extraño" es un MenuItem. La matriz MenuItem ya está generando un hijo MenuItem, pero su DataTemplate agrega una segunda. Prueba esto:

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

Nota del simplificada DataTemplate que sólo contiene un TextBlock, y el ItemContainerStyle para establecer las propiedades de la MenuItem generado.

+0

Esto parece prometedor, pero recibo una excepción que indica que * Un estilo destinado para el tipo 'MenuItem' no se puede aplicar al tipo 'ContentPresenter' *. ¿Tienes una idea de cómo arreglar eso? –

+0

No leí tu código correctamente. Realmente no se puede mezclar elementos generados con los no generados. Deshágase de ItemsControl y en su lugar, vincule el elemento de menú de nivel superior.Solo inserte los elementos no generados, como Preferencias, en la colección de elementos del menú, o use una Colección compuesta para mantenerlos separados. –

+0

Intenté usar CompositeCollection, pero no puedo vincular CollectionContainer a mi máquina virtual debido a un error en wpf. No quiero poner el otro MenuItem en la lista porque no pertenecen allí. Así que, al final, acabo de crear un submenú 'Archivos recientes' (ya lo tenía resuelto, pero realmente quería tener la lista en el menú 'Archivo'). –

Cuestiones relacionadas