views:

1061

answers:

2

I'm new to both WPF and MVVM. I searched for a good way to dynamically create menus in the MVVM partern and, not finding anything to my liking, rolled my own solution. It works, but for some reason the Foreground (text) color of the menus is sometimes (just sometimes) not correct.

I was going to include a picture to demonstrate my problem, but I don't have enough street cred on StackOverflow to do that. I therefore will add a link so you can call up a picture.

http://img220.imageshack.us/img220/1912/badmenu.jpg

My lowest submenu displays correctly with a white foreground, but its parent menu forground turned to black and is almost impossible to read. If I had hard coded the menus then the parent's forground color would be white. If I move my mouse over the parent its text will switch back to white, an the submenu will become black.

Further, once I move my mouse away from the parent, all of its boolean properties (IsHighlighted, IsSubmenuOpen, etc...) become false, which surprised me because I would think they should stay true. The end result is I haven't been able to solve this with a style trigger.

Here is my XAML .

<Window.Resources>
  <DataTemplate DataType="{x:Type src:ParentMenu}" >
  <Menu >
      <MenuItem Header="{Binding MenuName}" 
                ItemsSource="{Binding ChildMenuItems}" />
  </Menu>
</DataTemplate>

<HierarchicalDataTemplate DataType="{x:Type src:ChildMenu}" 
                          ItemsSource="{Binding ChildMenuItems}" >
  <MenuItem Header="{Binding MenuName}" Command="{Binding Path=Command}" />
</HierarchicalDataTemplate>

' StackOverflow is masking my end tag for Window.Resources

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

  <Grid>
       <!-- Add additional content here -->
  </Grid>
</DockPanel>

Both ParentMenu and ChildMenu inherit from a common class that actually holds all the menus and exposes sub-menus through the ChildMenuItems collection. ChildMenuItems is a list of ChildMenu objects. My ViewModels exposes a list of ParentMenu objects.

There probably is a better way to accomplish what I want here. As I said before, I'm new to both WPF and MVVM and this is the best solution I could construct so far. Currently I have a workaround where I set the background color of all menus to another color so the black text is readable, but that looks goofy. Here is a URL showing what I mean. I would make it a link, but StackOverflow is limiting me to only one link.

img132.imageshack.us/img132/4160/bettermenu.jpg

Any suggestions on what I'm doing wrong and/or how to fix the display problem?

A: 

As requested, here are my ViewModels.

ViewModelBase is the standard one created by studio. MainVieModel has got just enough to in it to create the test menus I was using to experiment with.

Basically I am working towards creating a Parent/Child menu classes I can use with a series of apps we sell to a broad collection of clients. I hope to make it where each customer can have a customizable collection of menus based upon their needs and which moudles they've purchased licenses for.

public abstract class ViewModelBase : INotifyPropertyChanged
{
 public event PropertyChangedEventHandler PropertyChanged;

 protected void OnPropertyChanged(string propertyName)
 {
  PropertyChangedEventHandler handler = PropertyChanged;

  if (handler != null)
  {
   handler(this, new PropertyChangedEventArgs(propertyName));
  }
 }
}

public class MainViewModel : ViewModelBase

{

    public MainViewModel()  { MakeMenus(); }

 private void MakeMenus()
 {
  // Makes some dummy menus to test with.
  Menus = new ObservableCollection<MyMenuItem>();
  ParentMenu parent;
  ChildMenu child;

  parent = new ParentMenu("First Level");
  Menus.Add(parent);
  child = new ChildMenu(parent, "second level");
  parent.ChildMenuItems.Add(child);
  ChildMenu child2 = new ChildMenu(child, "third level");
  child2.MenuCommand = new DelegateCommand(CommandTest,
                                                   CommandCanExecute_First);
  child.ChildMenuItems.Add(child2);

  child = new ChildMenu(parent, "second level 2");
  parent.ChildMenuItems.Add(child);
  child2 = new ChildMenu(child, "third level 2");
  child2.MenuCommand = new DelegateCommand(CommandTest, 
                                       CommandCanExecute_Second);
  child.ChildMenuItems.Add(child2);

  parent = new ParentMenu("Another First");
  parent.ChildMenuItems.Add(new ChildMenu(parent, "Another Second"));
  Menus.Add(parent);
  OnPropertyChanged("Menus");
 }

 private bool ExecuteToggle { get; set; }
 private void CommandTest() { ExecuteToggle = !ExecuteToggle; } 
 public ObservableCollection<MyMenuItem> Menus  {  get; private set;}
 public bool CommandCanExecute_First() { return ExecuteToggle; }
 public bool CommandCanExecute_Second() { return !ExecuteToggle;  }
}
RB Davidson
+1  A: 

The problem is that your VMs automatically get wrapped in MenuItems, so you essentially have MenuItems nested as the Header of MenuItems.

You can get around this by defining a Style (and pointing to it via ItemContainerStyle) that DataBinds to your VMs (Name to Header, DelegateCommands to Command, etc.) using MenuItem as the DataType.

An example of a way you can do this is below. Note that I've dropped the HierarchicalDataTemplate in favor of an ItemContainerStyle. I also took the liberty of defining a DataTemplate for your MainViewModel as it wasn't very clear how that was data bound.

<Window.Resources>
 <DataTemplate DataType="{x:Type src:MainViewModel}">
  <ItemsControl ItemsSource="{Binding Menus}"></ItemsControl>
 </DataTemplate>
 <DataTemplate DataType="{x:Type src:ParentMenu}" >
  <Menu>
   <MenuItem Header="{Binding Name}" 
        ItemsSource="{Binding ChildMenuItems}" ItemContainerStyle="{DynamicResource ChildMenuItemStyle}" />
  </Menu>
 </DataTemplate>
 <Style x:Key="ChildMenuItemStyle" TargetType="MenuItem">
  <Setter Property="Header" Value="{Binding Name}"></Setter>
  <Setter Property="ItemsSource" Value="{Binding ChildMenuItems}"></Setter>
 </Style>
</Window.Resources>

I've also cut some of the Command binding out for simplicity, but you can add it back in as necessary.

micahtan
It took a little bit of tweaking, but this worked. Thank you.One tweak were that you had Header binding to Name rather than the MenuName property. The other was that the DataTemplate for MainViewModel did not seem to do anything in my code. I tired several different ways, but I needed a "<Menu..." in the Dock for anything to display, and the DataTemplate had no effect I could tell.I tried binding to an ItemsControl, but then the menus stack vertically rather than lay out horizontally.Thanks again for you elegant solution.
RB Davidson