2009-09-08 17 views
14

Soy nuevo en WPF y MVVM. Estoy trabajando con un equipo en la aplicación LoB. Nos gustaría tener un control dinámico Menu, que crea el menú basado en el perfil de usuario registrado. En escenarios de desarrollo anteriores (a saber, ASP.NET), utilizamos para iterar a través de los datos que describe la recopilación y generar MenuItem de forma dinámica. En MVVM, ¿cómo podría hacer esto? ¿Puedo separar la vista XAML de ViewModel, que describe los elementos del menú?Interfaz de usuario de menú dinámico MVVM vinculante con ViewModel

Solución:

Con aportaciones de los comentaristas que fueron capaces de unirse Menu dinámicamente con los datos del modelo de vista. Este article fue de gran ayuda también.

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 clase de datos:

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

Después de pasar algún tiempo en Google, encontré que HierarchicalDataTemplate podría ser útil en la creación de menús dinámicos y, sin embargo, separar "preocupaciones" con el patrón de MVVM. Todavía no tengo ejemplos de código :( – Raj

Respuesta

14

intentar algo como esto:

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

    public string Text { get; set; } 

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

asumir que su DataContext tiene una propiedad llamada MenuItems que es una lista de MenuItemViewModel. Algo como esto debería funcionar, entonces:

<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

Hola con esto podría vincular mi clase a Menú, pero ¿cómo podría estructurarlos jerárquicamente? Cada elemento de menú tiene un parentId, aquellos con null parentId son elementos del menú raíz y quiero para organizar el menú resto debajo de ellos. ¿hay algún otro recurso en HierarchicalDataTemplate? Gracias – Raj

+0

Y también cómo podría asignar el estilo de XAML a MENUITEM ya que no se definen explícitamente en el XAML? – Raj

+0

Estilos de Menultem se puede asignar con propiedad ItemContainerStyle de Menú – Raj

4

Sé que esto es una entrada antigua pero necesito esto además de cómo enlazar comandos.

En cuanto a la pregunta de Guge sobre cómo enlazar Comandos: VMMenuItems es una propiedad en mi punto de vista de clase del modelo de tipo

ObservableCollection<Menu> 

y menú es la clase definida anteriormente. La propiedad de comando de MenuItem está vinculada a la propiedad de comando de la clase de menú. En mi punto de vista de clase del modelo

Menu.Command = _fou 

donde

private ICommand _fou; 

El 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

Esto debe llegar a donde usted va

<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}"/> 

Tenga en cuenta que en mi ejemplo, mi elemento de menú tiene una propiedad de tipo ICommand llamada Comando.

1

Si se está preguntando cómo hacer separadores, es realmente bastante fácil.

El código siguiente es parte de mi modelo de vista. Desde XAML utiliza la reflexión todo lo que tiene que hacer es volver 'objeto' que puede ser un MenuItemViewModel, Separator, o (si por alguna razón extraña que necesitaba) un real MenuItem.

estoy usando yield para generar dinámicamente los artículos ya que sólo parece a leer mejor para mí.Aunque estoy usando yield - si los elementos cambian, todavía necesito plantear un evento PropertyChanged para "ContextMenu" como de costumbre, pero no genero innecesariamente la lista hasta que sea necesario.

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 No se necesita ningún código en el código detrás y eso hace que sea más simple solución.

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

Y Menultem se representa como:

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() 
     { 

     } 
    } 

Los MenuItems concretas pueden ser instanciadas directamente o se puede hacer sus propios subtipos través de la herencia.

Cuestiones relacionadas