2009-07-01 10 views
15

Cuando Ato elementos de menú con un ObservableCollection, sólo el área "interior" de la Menultem se puede hacer clic:¿Cómo puedo vincular una ObservableCollection de ViewModels a un MenuItem?

alt text http://tanguay.info/web/external/mvvmMenuItems.png

En mi Ver que tienen este menú:

<Menu> 
    <MenuItem 
     Header="Options" ItemsSource="{Binding ManageMenuPageItemViewModels}" 
       ItemTemplate="{StaticResource MainMenuTemplate}"/> 
</Menu> 

Entonces Lo enlace con este DataTemplate:

<DataTemplate x:Key="MainMenuTemplate"> 
    <MenuItem 
     Header="{Binding Title}" 
     Command="{Binding DataContext.SwitchPageCommand, 
     RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}" 
     Background="Red" 
     CommandParameter="{Binding IdCode}"/> 
</DataTemplate> 

Puesto que cada modelo de vista en los ObservableCollection ManageMenuPageItemViewModels tiene una propiedad Título y CÓDIGO ID, el código anterior funciona bien a primera vista.

Sin embargo, el problema es que el MenuItem en el DataTemplate es en realidad dentro otro MenuItem (como si se está obligado dos veces) de modo que en el DataTemplate anteriormente con Antecedentes =" Rojo " hay un cuadro rojo dentro de cada elemento de menú y solo se puede hacer clic en esta área, no toda el área del elemento del menú (por ejemplo, si el usuario hace clic en el área donde está o hacia la derecha o izquierda del cl interior área indescifrable, entonces no pasa nada, que, si no tiene un color separado, es muy confuso.)

¿Cuál es la forma correcta de encuadernar los artículos de menú a una colección observable de modelos de vista para que todo el área dentro de cada elemento de menú sea clicable?

ACTUALIZACIÓN:

Así que hizo los siguientes cambios en función de consejo abajo y ahora tienen la siguiente:

alt text http://tanguay.info/web/external/mvvmMenuItemsYellow.png

sólo tengo un TextBlock dentro de mi DataTemplate, pero todavía no puedo "colorear toda la Menultem", pero sólo el TextBlock:

<DataTemplate x:Key="MainMenuTemplate"> 
    <TextBlock Text="{Binding Title}"/> 
</DataTemplate> 

Y puse el Comando de unión en Menu.ItemCo ntainerStyle pero no se activan ahora:

<Menu DockPanel.Dock="Top"> 
    <Menu.ItemContainerStyle> 
     <Style TargetType="MenuItem"> 
      <Setter Property="Background" Value="Yellow"/> 
      <Setter Property="Command" Value="{Binding DataContext.SwitchPageCommand, 
     RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"/> 
      <Setter Property="CommandParameter" Value="{Binding IdCode}"/> 
     </Style> 
    </Menu.ItemContainerStyle> 
    <MenuItem 
     Header="MVVM" ItemsSource="{Binding MvvmMenuPageItemViewModels}" 
       ItemTemplate="{StaticResource MainMenuTemplate}"/> 
    <MenuItem 
     Header="Application" ItemsSource="{Binding ApplicationMenuPageItemViewModels}" 
       ItemTemplate="{StaticResource MainMenuTemplate}"/> 
    <MenuItem 
     Header="Manage" ItemsSource="{Binding ManageMenuPageItemViewModels}" 
       ItemTemplate="{StaticResource MainMenuTemplate}"/> 
</Menu> 

Respuesta

35

Encontré que usar MVVM con MenuItems es muy desafiante. El resto de mi aplicación usa DataTemplates para emparejar la Vista con ViewModel, pero eso no parece funcionar con Menus debido exactamente a las razones que usted ha descrito. Así es como finalmente lo resolví. Mi punto de vista es el siguiente:

<DockPanel> 
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=(local:MainViewModel.MainMenu)}"> 
    <Menu.ItemContainerStyle> 
     <Style> 
      <Setter Property="MenuItem.Header" Value="{Binding Path=(contracts:IMenuItem.Header)}"/> 
      <Setter Property="MenuItem.ItemsSource" Value="{Binding Path=(contracts:IMenuItem.Items)}"/> 
      <Setter Property="MenuItem.Icon" Value="{Binding Path=(contracts:IMenuItem.Icon)}"/> 
      <Setter Property="MenuItem.IsCheckable" Value="{Binding Path=(contracts:IMenuItem.IsCheckable)}"/> 
      <Setter Property="MenuItem.IsChecked" Value="{Binding Path=(contracts:IMenuItem.IsChecked)}"/> 
      <Setter Property="MenuItem.Command" Value="{Binding}"/> 
      <Setter Property="MenuItem.Visibility" Value="{Binding Path=(contracts:IMenuItem.Visible), 
       Converter={StaticResource BooleanToVisibilityConverter}}"/> 
      <Setter Property="MenuItem.ToolTip" Value="{Binding Path=(contracts:IMenuItem.ToolTip)}"/> 
      <Style.Triggers> 
       <DataTrigger Binding="{Binding Path=(contracts:IMenuItem.IsSeparator)}" Value="true"> 
        <Setter Property="MenuItem.Template"> 
         <Setter.Value> 
          <ControlTemplate TargetType="{x:Type MenuItem}"> 
           <Separator Style="{DynamicResource {x:Static MenuItem.SeparatorStyleKey}}"/> 
          </ControlTemplate> 
         </Setter.Value> 
        </Setter> 
       </DataTrigger> 
      </Style.Triggers> 
     </Style> 
    </Menu.ItemContainerStyle> 
</Menu> 
</DockPanel> 

Si te fijas, que define una interfaz llamada IMenuItem, que es el modelo de vista para un MenuItem. Aquí está el código para eso:

public interface IMenuItem : ICommand 
{ 
    string Header { get; } 
    IEnumerable<IMenuItem> Items { get; } 
    object Icon { get; } 
    bool IsCheckable { get; } 
    bool IsChecked { get; set; } 
    bool Visible { get; } 
    bool IsSeparator { get; } 
    string ToolTip { get; } 
} 

en cuenta que la IMenuItem define IEnumerable Los productos, que es como se obtiene submenús. Además, IsSeparator es una forma de definir separadores en el menú (otro pequeño truco). Puede ver en el xaml cómo usa un DataTrigger para cambiar el estilo al estilo de separador existente si IsSeparator es verdadero. Así es como define la propiedad MainViewModel MainMenu (que la vista se une a):

public IEnumerable<IMenuItem> MainMenu { get; set; } 

Esto parece funcionar bien. Supongo que podría usar una ObservableCollection para MainMenu. En realidad estoy usando MEF para componer el menú de partes, pero después de eso los elementos en sí mismos son estáticos (aunque las propiedades de cada elemento del menú no lo son). También uso una clase AbstractMenuItem que implementa IMenuItem y es una clase de ayuda para crear instancias de elementos de menú en las diversas partes.

ACTUALIZACIÓN:

Con respecto a su problema de color, hace this thread ayuda?

+1

+1 - muy bueno para el ejemplo completo con Separadores y todo. –

+0

Tengo un diseño muy similar y todo funciona, excepto para mostrar el separador. Si cambio la plantilla a , entonces veo "Separador" donde debería estar el separador. Pero cuando trato de usar la plantilla como en tu respuesta, no se muestra nada. Intenté y luego el separador es visible, pero quiero el estilo predeterminado con el ancho establecido dinámicamente por el ancho del menú. ¿Se supone que debo definir SeparatorStyleKey en alguna parte? Busqué en línea pero no pude encontrar nada que me ayudó ...¡Gracias! – Dina

14

no pone el MenuItem en el DataTemplate. El DataTemplate define el contenido del MenuItem. En su lugar, especificar las propiedades extrañas para el MenuItem a través de la ItemContainerStyle:

<Menu> 
    <Menu.ItemContainerStyle> 
     <Style TargetType="MenuItem"> 
      <Setter Property="Header" Value="{Binding Title}"/> 
      ... 
     </Style> 
    </Menu.ItemContainerStyle> 
    <MenuItem 
     Header="Options" ItemsSource="{Binding ManageMenuPageItemViewModels}" 
       ItemTemplate="{StaticResource MainMenuTemplate}"/> 
</Menu> 

También, echar un vistazo a HierarchicalDataTemplate s.

+0

¿quiere decir definir el encabezado/color en el Menu.ItemContainerStyle y luego dentro de DataTemplate poner una HierarchicalDataTemplate que define el Command y CommandParameter? –

+0

Gracias, esto es exactamente lo que estaba buscando. Funciona genial. ¡Gracias! –

+0

+1 - HierarchicalDataTemplates hace que este problema sea casi trivial. –

2

Así es como he hecho mis menús. Puede que no sea precisamente lo que necesitas, pero creo que es bastante cercano.

<Style x:Key="SubmenuItemStyle" TargetType="MenuItem"> 
    <Setter Property="Header" Value="{Binding MenuName}"></Setter> 
    <Setter Property="Command" Value="{Binding Path=MenuCommand}"/> 
    <Setter Property="ItemsSource" Value="{Binding SubmenuItems}"></Setter> 
    </Style> 

    <DataTemplate DataType="{x:Type systemVM:TopMenuViewModel}" > 
    <Menu> 
     <MenuItem Header="{Binding MenuName}"   
        ItemsSource="{Binding SubmenuItems}" 
        ItemContainerStyle="{DynamicResource SubmenuItemStyle}" /> 
    </Menu> 
    </DataTemplate> 

    <Menu DockPanel.Dock="Top" ItemsSource="{Binding Menus}" /> 

TopMenuViewModel es una colección de los menús que aparecerán en la barra de menú. Cada uno de ellos contiene el MenuName que se mostrará y una colección llamada SubMenuItems que establezco como ItemsSource.

Yo controlo la manera en que los SubMenuItems se muestran por medio del estilo SumMenuItemStyle. Cada SubMenuItem tiene su propia propiedad MenuName, la propiedad Command de tipo ICommand y posiblemente otra colección de SubMenuItems.

El resultado es que puedo almacenar toda la información de mi menú en una base de datos y cambiar dinámicamente los menús que se muestran en el tiempo de ejecución. Se puede hacer clic en todo el área del menú y se visualiza correctamente.

Espero que esto ayude.

2

Simplemente haga que su DataTemplate sea un TextBlock (o tal vez un panel de pila con un icono y un TextBlock).

+0

ok eso es genial, funciona (pensé que lo había intentado), pero ahora tengo que conectar el comando de alguna manera al TextBlock, no tiene un atributo Command, no puedo usar mi DelegateCommand, ¿qué usaste? ¿Comportamientos adjuntos o algo más? –

+1

Edite el ItemContainerStyle –

+0

edítelo en consecuencia, se ha publicado una captura de pantalla, todavía no funciona :-( –

Cuestiones relacionadas