2009-07-01 5 views
15

Wenn ich Menüpunkte mit einer ObservableCollection binden, nur die "inneren" Bereich des MenuItem ist anklickbar:Wie kann ich eine ObservableCollection von ViewModels an ein MenuItem binden?

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

In meinem Ansicht ich dieses Menü haben:

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

Dann Ich binde es mit diesem 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> 

Da jedes Ansichtsmodell in dem ObservableCollection ManageMenuPageItemViewModels hat eine Eigenschaft Titel und IDCODE, der obige Code funktioniert auf dem ersten Blick in Ordnung.

JEDOCH, ist das Problem, dass die MenuItem in der Datatemplate tatsächlich innen andere MenuItem ist (, als ob es zweimal gebunden wird), so dass in der obigen Datatemplate mit BACKGROUND =“ Rot " gibt es eine Rote Box in jedem Menüpunkt und nur dieser Bereich kann angeklickt werden, nicht der gesamte Menübereich selbst (zB wenn der Benutzer auf den Bereich klickt, wo sich das Häkchen befindet oder rechts oder links) der inneren cl ickable Bereich, dann passiert nichts, was, wenn Sie nicht eine separate Farbe haben, ist sehr verwirrend.)

Was ist die richtige Methode, um MenuItems an eine ObservableCollection von ViewModels zu binden, so dass der gesamte Bereich in jedem MenuItem ist anklickbar?

UPDATE:

Also habe ich die folgenden Änderungen auf Beratung basierend unten und jetzt haben diese:

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

ich nur einen Textblock in meinem Datatemplate, aber ich kann immer noch nicht „Farbe das ganze MenuItem“ aber nur den Textblock:

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

und ich habe die Befehlsbindung in Menu.ItemCo ntainerStyle aber sie jetzt nicht schießen:

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

Antwort

35

Ich fand MVVM mit MenuItems als sehr herausfordernd. Der Rest meiner Anwendung verwendet DataTemplates, um die Ansicht mit dem ViewModel zu paaren, aber das scheint aus genau den Gründen, die Sie beschrieben haben, nicht mit Menüs zu funktionieren. So habe ich es schließlich gelöst. Meine Ansicht sieht wie folgt aus:

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

Wenn Sie bemerken, ich eine Schnittstelle definiert genannt IMenuItem, die das Ansichtsmodell für ein MenuItem ist. Hier ist der Code für das:

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

Beachten Sie, dass die IMenuItem IEnumerable Artikel definiert, das ist, wie Sie Untermenüs erhalten. Der IsSeparator ist auch eine Möglichkeit, Separatoren im Menü zu definieren (ein weiterer harter kleiner Trick). Sie können in der XAML sehen, wie ein DataTrigger verwendet wird, um den Stil in den vorhandenen Trennstil zu ändern, wenn IsSeparator den Wert true hat. Hier ist, wie MainViewModel die MainMenu Eigenschaft definiert (dh die Ansicht bindet an):

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

Dies scheint gut zu funktionieren. Ich nehme an, Sie könnten eine ObservableCollection für das MainMenu verwenden. Ich verwende tatsächlich MEF, um das Menü aus Teilen zusammenzusetzen, aber danach sind die Elemente selbst statisch (obwohl die Eigenschaften der einzelnen Menüpunkte nicht sind). Ich verwende auch eine AbstractMenuItem-Klasse, die IMenuItem implementiert und eine Hilfsklasse ist, um Menüelemente in den verschiedenen Teilen zu instanziieren.

UPDATE:

Ihr Farbenproblem betrifft, so tut this thread Hilfe?

+1

+1 - sehr schön für das vollständige Beispiel mit Separatoren und alles. –

+0

Ich habe ein sehr ähnliches Design und alles funktioniert bis auf den Separator. Wenn ich die Vorlage zu ändere, sehe ich "Trennzeichen", wo ein Trennzeichen sein sollte. Aber wenn ich versuche, die Vorlage wie in Ihrer Antwort zu verwenden, wird nichts angezeigt. Ich habe versucht, und dann ist das Trennzeichen sichtbar, aber ich möchte den Standard-Stil mit der Breite dynamisch durch die Menübreite festgelegt. Soll ich SeparatorStyleKey irgendwo definieren? Ich suchte online, konnte aber nichts finden, was half ...Vielen Dank! – Dina

14

nicht die MenuItem im DataTemplate setzen Sie. Die DataTemplate definiert den Inhalt der MenuItem. Stattdessen geben Fremd Eigenschaften für die MenuItem über die 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> 

Werfen Sie auch einen Blick auf HierarchicalDataTemplate s.

+0

meinst du die Header/Farbe im Menu.ItemContainerStyle definieren und dann innerhalb der DataTemplate eine HierarchicalDataTemplate setzen, die den Command und CommandParameter definiert? –

+0

Danke, das ist genau was ich gesucht habe. Funktioniert super. Vielen Dank! –

+0

+1 - HierarchicalDataTemplates machen dieses ganze Problem fast trivial. –

2

Hier ist, wie ich meine Menüs gemacht habe. Es ist vielleicht nicht genau das, was du brauchst, aber ich denke, es ist ziemlich nah.

TopMenuViewModel ist eine Sammlung der Menüs, die in der Menüleiste angezeigt werden. Sie enthalten jeweils den MenuName, der angezeigt wird, und eine Auflistung namens SubMenuItems, die ich als ItemsSource festgelegt habe.

Ich kontrolliere die Art und Weise wie die SubMenuItems über den Stil SumMenuItemStyle angezeigt werden. Jedes SubMenuItem besitzt eine eigene MenuName-Eigenschaft, eine Command-Eigenschaft vom Typ ICommand und möglicherweise eine weitere Auflistung von SubMenuItems.

Das Ergebnis ist, dass ich in der Lage bin, alle meine Menüinformationen in einer Datenbank zu speichern und dynamisch zu wechseln, welche Menüs zur Laufzeit angezeigt werden. Der gesamte Menüpunkt ist anklickbar und wird korrekt angezeigt.

Hoffe, das hilft.

2

Machen Sie einfach Ihre DataTemplate zu einem TextBlock (oder einem Stack Panel mit einem Icon und einem TextBlock).

+0

ok, das ist großartig, es funktioniert (ich dachte, ich hätte das versucht), aber jetzt muss ich den Befehl irgendwie an den TextBlock anschließen, er hat kein Command-Attribut, ich kann meinen DelegateCommand nicht benutzen, was hast du benutzt AttachedBehaviors oder etwas anderes? –

+1

Editiere den ItemContainerStyle –

+0

editiert es oben, gepostete screenshot, funktioniert immer noch nicht :-( –

Verwandte Themen