2009-09-08 11 views
14

Jestem nowy w WPF i MVVM. Pracuję z zespołem w aplikacji LoB. Chcielibyśmy mieć kontrolkę dynamiczną Menu, która tworzy menu na podstawie zalogowanego profilu użytkownika. W poprzednich scenariuszach rozwoju (mianowicie ASP.NET) używamy do iteracji poprzez dane, które opisują gromadzenie i dynamiczne generowanie MenuItem. W MVVM jak mam to zrobić? Czy mogę oddzielić widok XAML od ViewModel, który opisuje elementy menu?Dynamiczny interfejs użytkownika MVVM od wiązania z ViewModel

Rozwiązanie:

z wejściami z komentatorów ja byliśmy w stanie związać Menu dynamicznie z danymi z ViewModel. Ta article również była bardzo pomocna.

XAML:

<HierarchicalDataTemplate DataType="{x:Type self:Menu}" ItemsSource="{Binding Path=Children, UpdateSourceTrigger=PropertyChanged}"> 
    <ContentPresenter Content="{Binding Path=MenuText}" RecognizesAccessKey="True"/> 
</HierarchicalDataTemplate> 

[...] 

<Menu Height="21" Margin="0" Name="mainMenu" VerticalAlignment="Top" HorizontalAlignment="Stretch" 
     ItemsSource="{Binding Path=MenuItems, UpdateSourceTrigger=PropertyChanged}" ItemContainerStyle="{StaticResource TopMenuItems}"> 
    <Menu.Background> 
     <ImageBrush ImageSource="/Wpf.Modules;component/Images/MenuBg.jpg" /> 
    </Menu.Background> 
</Menu> 

Menu klasa dane:

public class Menu : ViewModelBase 
{ 
    public Menu() 
    { 
     IsEnabled = true; 
     Children = new List<Menu>(); 
    } 

    #region [ Menu Properties ] 

    private bool _isEnabled; 
    private string _menuText; 
    private ICommand _command; 
    private IList<Menu> _children; 

    public string MenuText 
    { 
     get { return _menuText; } 
     set 
     { 
      _menuText = value; 
      base.OnPropertyChanged("MenuText"); 
     } 
    } 

    public bool IsEnabled 
    { 
     get { return _isEnabled; } 
     set 
     { 
      _isEnabled = value; 
      base.OnPropertyChanged("IsEnabled"); 
     } 
    } 

    public ICommand Command 
    { 
     get { return _command; } 
     set 
     { 
      _command = value; 
      base.OnPropertyChanged("Command"); 
     } 
    } 

    public IList<Menu> Children 
    { 
     get { return _children; } 
     set 
     { 
      _children = value; 
     } 
    } 

    #endregion 
} 
+0

Po spędzeniu trochę czasu w Google, stwierdziłem, że HierarchicalDataTemplate może być pomocny przy tworzeniu dynamicznego menu, a jednocześnie oddziela "obawy" od wzorca MVVM. Nie mam jeszcze próbek kodu :( – Raj

Odpowiedz

14

Spróbuj czegoś takiego:

public class MenuItemViewModel 
{ 
    public MenuItemViewModel() 
    { 
     this.MenuItems = new List<MenuItemViewModel>(); 
    } 

    public string Text { get; set; } 

    public IList<MenuItemViewModel> MenuItems { get; private set; } 
} 

Załóżmy, że Twój DataContext ma właściwość o nazwie elementów menu, które jest lista MenuItemViewModel. Coś takiego powinno zadziałać, a następnie:

<Window x:Class="WpfApplication1.Window1" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:self="clr-namespace:WpfApplication1" 
     Title="Window1" Height="300" Width="300"> 
    <Window.Resources> 
     <HierarchicalDataTemplate DataType="{x:Type self:MenuItemViewModel}" 
            ItemsSource="{Binding Path=MenuItems}"> 
      <ContentPresenter Content="{Binding Path=Text}" /> 
     </HierarchicalDataTemplate> 
    </Window.Resources> 
    <DockPanel> 
     <Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=MenuItems}" /> 
     <Grid /> 
    </DockPanel> 
</Window> 
+0

Cześć, mógłbym związać moją klasę z Menu, ale w jaki sposób mogłem je hierarchicznie uformować? Każdy element menu ma identyfikator parentId, te z pustym parentId są elementami menu root i chcę do zorganizowania menu odpoczynku pod nimi.Czy istnieje inny zasób na HierarchicalDataTemplate? Dzięki – Raj

+0

A także w jaki sposób można przypisać styl z XAML do MenuItem, ponieważ nie są one jawnie zdefiniowane w XAML? – Raj

+0

Style do MenuItem można przypisać za pomocą właściwości ItemContainerStyle Menu: – Raj

4

Wiem, że to stary post, ale potrzebuję tego i jak wiązać polecenia.

Co do pytania dotyczącego Guge jak powiązać polecenia: VMMenuItems jest nieruchomość w moim widoku modelu klasy typu

ObservableCollection<Menu> 

i Menu jest klasa zdefiniowano powyżej. Właściwość CommandItem MenuItem jest powiązana z właściwością Command klasy Menu. moim zdaniem modelu klasy

Menu.Command = _fou 

gdzie

private ICommand _fou; 

XAML

<ListView.ContextMenu> 
    <ContextMenu ItemsSource="{Binding Path=VMMenuItems}"> 
      <ContextMenu.ItemContainerStyle> 
       <Style TargetType="{x:Type MenuItem}">          
         <Setter Property="Command" Value="{Binding Command}"/> 
        </Style> 
      </ContextMenu.ItemContainerStyle> 
     </ContextMenu>      
</ListView.ContextMenu> 
13

To powinno cię dokąd idziesz

<UserControl x:Class="WindowsUI.Views.Default.MenuView" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:ViewModels="clr-namespace:WindowsUI.ViewModels" 
     mc:Ignorable="d" 
     d:DesignHeight="300" d:DesignWidth="300"> 
<UserControl.Resources> 
    <Style TargetType="{x:Type MenuItem}"> 
     <Setter Property="Header" Value="{Binding Path=DisplayName}"/> 
     <Setter Property="Command" Value="{Binding Path=Command}"/> 
    </Style> 
    <HierarchicalDataTemplate 
     DataType="{x:Type ViewModels:MenuItemViewModel}" 
     ItemsSource="{Binding Path=Items}"> 
    </HierarchicalDataTemplate> 
</UserControl.Resources> 
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=Items}"/> 

Należy zauważyć, że w moim przykładzie, moja pozycja menu ma właściwość typu ICommand o nazwie Command.

1

Jeśli zastanawiasz się, jak robić separatory, to naprawdę łatwo.

Poniższy kod jest częścią mojego ViewModel. Ponieważ XAML używa refleksji, wszystko, co muszę zrobić, to zwrócić "obiekt", który może być MenuItemViewModel, Separator, lub (jeśli z jakiegoś dziwnego powodu musiałem) rzeczywistym MenuItem.

Używam yield do dynamicznego generowania elementów, ponieważ wydaje mi się, że lepiej dla mnie czyta.Mimo że używam yield - jeśli elementy ulegają zmianie, nadal muszę jak zwykle podnosić PropertyChanged dla "ContextMenu", ale nie generuję niepotrzebnie listy, dopóki jej nie potrzebuję.

public IEnumerable<object> ContextMenu 
    { 
     get 
     { 
      // ToArray() needed or else they get garbage collected 
      return GetContextMenu().ToArray(); 
     } 
    } 

    public IEnumerable<object> GetContextMenu() 
    { 
     yield return new MenuItemViewModel() 
     { 
      Text = "Clear all flags", 
     }; 

     // adds a normal 'Separator' menuitem 
     yield return new Separator(); 

     yield return new MenuItemViewModel() 
     { 
      Text = "High Priority" 
     }; 

     yield return new MenuItemViewModel() 
     { 
      Text = "Medium Priority" 
     }; 

     yield return new MenuItemViewModel() 
     { 
      Text = "Low Priority" 
     }; 

     yield break; 
    } 
5

This solution nie potrzebuje żadnego kodu w kodzie tyłu i sprawia, że ​​prostsze rozwiązanie.

 <Menu> 
      <MenuItem ItemsSource="{Binding Path=ChildMenuItems}" Header="{Binding Path=Header}"> 
       <MenuItem.Resources> 
        <HierarchicalDataTemplate DataType="{x:Type vm:MenuItemViewModel}" ItemsSource="{Binding ChildMenuItems}"> 
         <MenuItem Header="{Binding Path=Header}" Command="{Binding Path=Command}"/> 
        </HierarchicalDataTemplate> 
        <DataTemplate DataType="{x:Type vm:SeparatorViewModel}"> 
         <Separator> 
          <Separator.Template> 
           <ControlTemplate> 
            <Line X1="0" X2="1" Stroke="Black" StrokeThickness="1" Stretch="Fill"/> 
           </ControlTemplate> 
          </Separator.Template> 
         </Separator> 
        </DataTemplate> 
       </MenuItem.Resources> 
      </MenuItem> 
     </Menu> 

I MenuItem jest reprezentowany jako:

public class MenuItemViewModel : BaseViewModel 
    { 
     /// <summary> 
     /// Initializes a new instance of the <see cref="MenuItemViewModel"/> class. 
     /// </summary> 
     /// <param name="parentViewModel">The parent view model.</param> 
     public MenuItemViewModel(MenuItemViewModel parentViewModel) 
     { 
      ParentViewModel = parentViewModel; 
      _childMenuItems = new ObservableCollection<MenuItemViewModel>(); 
     } 

     private ObservableCollection<MenuItemViewModel> _childMenuItems; 
     /// <summary> 
     /// Gets the child menu items. 
     /// </summary> 
     /// <value>The child menu items.</value> 
     public ObservableCollection<MenuItemViewModel> ChildMenuItems 
     { 
      get 
      { 
       return _childMenuItems; 
      } 
     } 

     private string _header; 
     /// <summary> 
     /// Gets or sets the header. 
     /// </summary> 
     /// <value>The header.</value> 
     public string Header 
     { 
      get 
      { 
       return _header; 
      } 
      set 
      { 
       _header = value; NotifyOnPropertyChanged("Header"); 
      } 
     } 

     /// <summary> 
     /// Gets or sets the parent view model. 
     /// </summary> 
     /// <value>The parent view model.</value> 
     public MenuItemViewModel ParentViewModel { get; set; } 

     public virtual void LoadChildMenuItems() 
     { 

     } 
    } 

Konkretne elementów menu można albo instancja bezpośrednio lub można zrobić własny podtypów drodze dziedziczenia.