2009-05-26 11 views
5

Mam scenariusz, w którym muszę mieć zarówno statyczne, jak i dynamiczne elementy menu. Elementy statyczne zostaną zdefiniowane w XAML, a elementy dynamiczne dostarczone przez Model widoku. Każdy dynamiczny element sam będzie reprezentowany przez VieModel, nazwijmy go CommandViewModel. CommandViewModel ma między innymi nazwę wyświetlaną, może również zawierać inne CommandViewModels.Mieszanie dynamicznych i statycznych pozycji menu XAML

MainViewModel że przyzwyczaja jako datacontext do menu jest następująca:

public class MainMenuViewModel : INotifyPropertyChanged 
{ 

    private ObservableCollection<CommandViewModel> m_CommandVMList; 


    public MainMenuViewModel() 
    { 
    m_ CommandVMList = new ObservableCollection<CommandViewModel>(); 

    CommandViewModel cmv = new CommandViewModel(); 
    cmv.DisplayName = "Dynamic Menu 1"; 
    m_CommandVMList.Add(cmv); 

    cmv = new CommandViewModel(); 
    cmv.DisplayName = "Dynamic Menu 2"; 
    m_CommandVMList.Add(cmv); 

    cmv = new CommandViewModel(); 
    cmv.DisplayName = "Dynamic Menu 3"; 
    m_CommandVMList.Add(cmv); 

    } 

    public ObservableCollection<CommandViewModel> CommandList 
    { 
    get { return m_CommandVMList; } 
    set 
    { 
     m_CommandVMList = value; 
     OnPropertyChanged("CommandList"); 
    } 
    } 

... ... ...

XAML Menu:

<Grid> 
    <Grid.Resources> 
    <HierarchicalDataTemplate DataType="{x:Type Fwf:CommandViewModel}" ItemsSource="{Binding Path=CommandViewModels}"> 
     <MenuItem Header="{Binding Path=DisplayName}"/> 
    </HierarchicalDataTemplate> 
    </Grid.Resources> 

    <Menu VerticalAlignment="Top" HorizontalAlignment="Stretch"> 
    <MenuItem Header="Static Top Menu Item 1"> 
     <MenuItem Header="Static Menu Item 1"/> 
     <MenuItem Header="Static Menu Item 2"/> 
     <MenuItem Header="Static Menu Item 3"/> 
     <ItemsControl ItemsSource="{Binding Path= CommandList}"/> 
     <MenuItem Header="Static Menu Item 4"/> 
     </MenuItem> 
    </Menu> 
</Grid> 

Wszystko działa dobrze z wyjątkiem że cokolwiek próbuję reprezentować listę dynamicznych menu, w tym przypadku ItemsControl, jest pokazywane w interfejsie jako JEDNO Menu Ite m conatining więcej pozycji menu, więc cały zbiór dynamicznych elementów menu zostanie wybrany po kliknięciu na przedmiot. Kolekcja jest poprawnie przedstawiana, ponieważ każdy dynamiczny element menu jest wyświetlany jako pozycja menu, ale w obrębie tego większego elementu menu. Myślę, że rozumiem, dlaczego menu tworzy po prostu element menu dla każdego z zawartych w nim elementów, statycznego lub dynamicznego, którego nie obchodzi. Czy istnieje sposób, aby każdy element menu dynamicznego był tworzony na tym samym poziomie i należący do elementu menu nadrzędnego, jak te statyczne w przykładzie?

Odpowiedz

5

Zamiast sztywnego kodowania "statycznych" pozycji menu po stronie XAML, zakodowałbym je po stronie VM jako obiekty CommandViewModel.

Odkąd tak mocno kodujesz, nie utracisz elastyczności, a zyskasz dodatkową korzyść dzięki utrzymaniu synchronizacji elementów statycznych z HierarchicalDataTemplate, jeśli zdecydujesz się renderować je inaczej w przyszłości .

Należy pamiętać, że może zajść potrzeba zmiany powiązań, aby menu było powiązane z kolekcją pozycji menu. Możesz znaleźć przykład tego here.

EDIT: Przykładowy kod

byłem w stanie włamać się to dość szybko, a większość z definicji klasy są niepełne (np INotifyPropertyChanged), ale powinien to daje wyobrażenie o tym, co można zrobić. Dodałem trochę zagnieżdżenia poleceń w trzecim poleceniu, aby upewnić się, że działa Hierarchiczny obiekt DataTemplate.

Oto XAML

<Window 
    x:Class="WPFDynamicMenuItems.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:WPFDynamicMenuItems" 
    Title="Window1" Height="300" Width="600"> 
    <Grid> 
     <Grid.Resources> 
      <HierarchicalDataTemplate DataType="{x:Type local:CommandViewModel}" ItemsSource="{Binding Path=CommandList}"> 
       <ContentPresenter 
        Content="{Binding Path=DisplayName}" 
        RecognizesAccessKey="True" /> 
      </HierarchicalDataTemplate> 
     </Grid.Resources> 
     <ToolBarTray> 
      <ToolBar> 
      <Menu> 
       <Menu.ItemsSource> 
        <CompositeCollection> 
         <MenuItem Header="A"></MenuItem> 
         <MenuItem Header="B"></MenuItem> 
         <MenuItem Header="C"></MenuItem> 

         <CollectionContainer x:Name="dynamicMenuItems"> 
         </CollectionContainer> 

         <MenuItem Header="D"></MenuItem> 

        </CompositeCollection> 
       </Menu.ItemsSource> 

      </Menu> 
       </ToolBar> 
     </ToolBarTray> 
    </Grid> 
</Window> 

A oto kod z opóźnieniem:

using System.Collections.ObjectModel; 
using System.ComponentModel; 
using System.Windows; 

namespace WPFDynamicMenuItems 
{ 
    /// <summary> 
    /// Interaction logic for Window1.xaml 
    /// </summary> 
    public partial class Window1 : Window 
    { 
     private MainMenuViewModel _mainMenuVM = new MainMenuViewModel(); 

     public Window1() 
     { 
      InitializeComponent(); 

      this.dynamicMenuItems.Collection = this._mainMenuVM.CommandList; 
     } 
    } 


    public class MainMenuViewModel : INotifyPropertyChanged 
    { 
     private ObservableCollection<CommandViewModel> m_CommandVMList; 

     public MainMenuViewModel() 
     { 
      m_CommandVMList = new ObservableCollection<CommandViewModel>(); 
      CommandViewModel cmv = new CommandViewModel(); 
      cmv.DisplayName = "Dynamic Menu 1"; 
      m_CommandVMList.Add(cmv); 
      cmv = new CommandViewModel(); 
      cmv.DisplayName = "Dynamic Menu 2"; 
      m_CommandVMList.Add(cmv); 
      cmv = new CommandViewModel(); 
      cmv.DisplayName = "Dynamic Menu 3"; 
      m_CommandVMList.Add(cmv); 

      CommandViewModel nestedCMV = new CommandViewModel(); 
      nestedCMV.DisplayName = "Nested Menu 1"; 
      cmv.CommandList.Add(nestedCMV); 

      nestedCMV = new CommandViewModel(); 
      nestedCMV.DisplayName = "Nested Menu 2"; 
      cmv.CommandList.Add(nestedCMV); 
     } 
     public ObservableCollection<CommandViewModel> CommandList 
     { 
      get { return m_CommandVMList; } 
      set { m_CommandVMList = value; OnPropertyChanged("CommandList"); } 
     } 

     protected void OnPropertyChanged(string propertyName) 
     { 
      // Hook up event... 
     } 

     #region INotifyPropertyChanged Members 

     public event PropertyChangedEventHandler PropertyChanged; 

     #endregion 
    } 

    public class CommandViewModel : INotifyPropertyChanged 
    { 
     private ObservableCollection<CommandViewModel> m_CommandVMList; 

     public CommandViewModel() 
     { 
      this.m_CommandVMList = new ObservableCollection<CommandViewModel>(); 
     } 

     public string DisplayName { get; set; } 

     public ObservableCollection<CommandViewModel> CommandList 
     { 
      get { return m_CommandVMList; } 
      set { m_CommandVMList = value; OnPropertyChanged("CommandList"); } 
     } 

     protected void OnPropertyChanged(string propertyName) 
     { 
      // Hook up event... 
     } 

     #region INotifyPropertyChanged Members 

     public event PropertyChangedEventHandler PropertyChanged; 

     #endregion 
    } 
} 
+0

Tak chciałabym zrobić, ale niestety nie mogę, to dlatego przede wszystkim jest, że XAML jest dość dużo z moich rąk. Jest tworzony/zarządzany przez programistów aplikacji, po prostu używa mojego modelu, aby rozszerzyć swoje menu o elementy, które muszą być sterowane z kodu. –

+0

To niefortunne. Czy istnieje sposób, aby zmienić XAML, aby skorzystać z CompositeCollection (http://msdn.microsoft.com/en-us/library/system.windows.data.compositecollection.aspx)? To nie jest idealne, ale może umożliwić wstrzykiwanie dynamicznych przedmiotów. Jedyną wadą, jaką widzę, jest sposób sortowania wynikowych elementów MenuItem, ale możesz to zrobić, traktując MenuItems, które są wcześniej jako jedna kolekcja, i MenuItems, które przychodzą potem jako kolejne. – micahtan

+0

Micahtan - może powinieneś napisać rozwiązanie z klasą CompositeCollection do swojego posta? Ponieważ wydaje się, że jest to naprawdę dobry sposób na zrobienie tego. Dzięki, mam ten sam problem. – arconaut

Powiązane problemy