2009-09-09 8 views
6

Mam "Plik" MenuItem czy chciałbym wyświetlić listę ostatnio otwieranych plików.Jak mogę włączyć listę elementów menuItem do innej pozycji menuItem w WPF?

Oto XAML mam teraz:

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

Jednak kiedy używam tego kodu, nie jest dziwne przesunięcie wokół MenuItem s, a wygląda na to, że to pojemnik wokół nich. Jak mogę się tego pozbyć?

Oto zrzut ekranu jak to wygląda:

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

+0

W tym artykule na blogu pokazano, jak tworzyć menu oparte na danych: http://weblogs.asp.net/okloeten/archive/2007/11/14/5149692.aspx –

Odpowiedz

4

Próbowałem przy użyciu CompositeCollection jak sugerował Kent Boogaart, ale nie mogłem pracować z powodu bug in wpf nie pozwalając użyć RelativeSource wiążącej w CollectionContainer.

Rozwiązanie użyłem jest mieć RecentFiles we własnym podmenu związany z Kolekcji poprzez własnością ItemsSource.

Naprawdę chciałem mieć listę w menu „Plik”, ale myślę, że jest to kolejna najlepsza rzecz ...

Edit

Zainspirowany this article I zbudował zwyczaj i bardziej ogólnie 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); 
     } 
    } 
    } 

} 

to dalekie od doskonałości, ale to działa na mnie. Możesz go skomentować ...

1

Spróbuj użyć HierarchicalDataTemplate z wewnętrznym ContentPresenter zamiast. Take a look at this SO answer for more details.

+0

odpowiedź, którą łączysz, jest naprawdę dla podmenu. Chciałbym, żeby lista dynamiczna była częścią niedynamicznego menu ... Masz jakiś inny pomysł? –

+0

Więc zasadniczo chcesz połączyć niektóre dynamiczne MenuItems w statycznie zdefiniowane Menu? AFAIK, gdy tworzysz bazę danych, albo wiążisz kompletny ItemSource, albo tworzysz zawartość ręcznie (albo w XAML, albo z kodu). To, co chciałbym zrobić, to uczynić całą bazę danych menu MenuItemViewModel, ale następnie upewnić się, że za pośrednictwem interfejsu API można edytować tylko listę dynamiczną. –

6

z "dziwne offset" jest MenuItem. Jednostka macierzysta MenuItem już wygenerowała dla ciebie dziecko MenuItem, ale Twoja DataTemplate dodaje drugą. Spróbuj tego:

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

Uwaga uproszczona DataTemplate że zawiera tylko TextBlock i ItemContainerStyle ustawić właściwości na wygenerowanym MenuItem.

+0

Wygląda to obiecująco, ale otrzymuję wyjątek stwierdzający, że * Styl przeznaczony dla typu "MenuItem" nie może być zastosowany do typu "ContentPresenter" *. Czy masz pomysł, jak to naprawić? –

+0

Nie odczytałem poprawnie kodu. Nie można naprawdę mieszać wygenerowanych elementów z nie wygenerowanymi. Pozbądź się ItemsControl i powiąż obiekt MenuItem najwyższego poziomu.Wystarczy przykleić nie wygenerowane elementy, takie jak Preferencje, do kolekcji elementów menu, lub użyć elementu CompositeCollection, aby zachować je oddzielnie. –

+0

Próbowałem użyć CompositeCollection, ale nie mogę powiązać CollectionContainer z moją maszyną wirtualną z powodu błędu w wpf. Nie chcę umieszczać innego MenuItem na liście, ponieważ tam nie pasują. Ostatecznie stworzyłem podmenu "Ostatnie pliki" (już to opracowałem, ale naprawdę chciałem mieć listę w menu "Plik"). –

Powiązane problemy