views:

54

answers:

2

I'm attempting to setup a MenuItem that will have a submenu of page numbers that can be selected. I want to bind the ItemsSource to a list of page numbers (actually to the PageCount with a converter creating the list) and then bind the IsChecked property of each MenuItem in the sub-menu to the PageIndex. My problem is with the second binding as it too requires a converter, but that converter need to know the page number that the MenuItem represents, but I cannot figure out how to pass that information to the converter. Here's what I've tried so far.

<MenuItem Header="_Goto Page" 
          ItemsSource="{Binding 
                        Path=CurrentImage.PageCount, 
                        Converter={StaticResource countToList}}">
    <MenuItem.ItemContainerStyle>
        <Style TargetType="MenuItem">
            <Setter Property="IsCheckable" 
                    Value="True"/>
            <Setter Property="IsChecked" 
                    Value="{Binding 
                            ElementName=MainWindow,
                            Path=CurrentImage.PageIndex, 
                            Mode=TwoWay,
                            Converter={StaticResource pageNumChecked},
                            ConverterParameter={Binding 
                                                RelativeSource={RelativeSource Self}, 
                                                Path=Content}}"/>
        </Style>
    </MenuItem.ItemContainerStyle>
</MenuItem>

Of course the problem is that the ConverterParameter cannot be bound as it is not a DependencyProperty. So my question is how can I pass in the information I need or is there another way to do this.

Note: I already tried putting the MenuItems inside of a ListBox which worked really well as far as the bindings are concerned, but caused the sub-menu to behave in a non-standard way. That is when you opened the sub-menu the entire ListBox was treated as one MenuItem.

Edit

So here's what I've gotten to work so far. I tried binding to a hidden ListBox but when I bound the MenuItem.ItemsSource to the 'ListBox.Items' I got the list of ints instead of a list of ListBoxItems which I needed to get the IsSelected property. So I ended up using Quartermeister's suggestion of using MultiBinding to get the IsChecked property to bind to the PageIndex in OneWay mode. To handle the other direction I used an event handler on the Click event. It's worth noting that at first I had IsCheckable set to true and was working with the Checked event, but that resulted is some odd behaviors.

<MenuItem x:Name="GotoPageMenuItem" Header="_Goto Page"
          ItemsSource="{Binding Path=CurrentImage.PageCount, 
                                Converter={StaticResource countToList}}">
    <MenuItem.ItemContainerStyle>
        <Style TargetType="MenuItem">
            <Setter Property="IsCheckable" 
                    Value="False"/>
            <EventSetter Event="Click"
                         Handler="GotoPageMenuItem_Click"/>
            <Setter Property="IsChecked">
                <Setter.Value>
                    <MultiBinding Converter="{StaticResource pageNumChecked}"
                                              Mode="OneWay">
                        <Binding RelativeSource="{RelativeSource FindAncestor, 
                                                  AncestorType={x:Type Window}}" 
                                                  Path="CurrentImage.PageIndex" 
                                                  Mode="OneWay"/>
                        <Binding RelativeSource="{RelativeSource Self}" 
                                                  Path="Header"
                                                  Mode="OneWay"/>
                    </MultiBinding>
                </Setter.Value>
            </Setter>
        </Style>
    </MenuItem.ItemContainerStyle>
</MenuItem>

And here's the GotoPageMenuItem_Click code

private void GotoPageMenuItem_Click(object sender, RoutedEventArgs e)
{
    var item = sender as MenuItem;
    if (item != null)
    {
        //If the item is already checked then we don't need to do anything
        if (!item.IsChecked)
        {
            var pageNum = (int)item.Header;
            CurrentImage.PageIndex = (pageNum - 1);
        }
    }
}
A: 

Can you do what you want using a MultiBinding?

<Setter Property="IsChecked">
    <Setter.Value>
        <MultiBinding Converter="{StaticResource pageNumChecked}">
            <Binding ElementName="MainWindow" Path="CurrentImage.PageIndex" Mode="TwoWay"/>
            <Binding RelativeSource="{RelativeSource Self}" Path="Content"/>
        </MultiBinding>
    </Setter.Value>
</Setter>

Have your pageNumChecked converter implement IMultiValueConverter instead of IValueConverter and Convert will get an array with the result of each child binding. In this case, it would be a two-element array where the first element is your current input, PageIndex, and the second index is your current ConverterParameter, Content. If you want two-way binding, ConvertBack will need to return a two-element array, and you would return Binding.DoNothing for the second parameter so that it does not try to update Content.

Quartermeister
This does not work for two-way binding because I need to know the `Content` value for both directions and it is only passed into the `Convert` and not the `ConvertBack`.
juharr
Even though this idea didn't work in both directions it got me half way there so, you win.
juharr
A: 

Sounds like you are trying to build dynamic menus that control the checked state of each menu item.
I extended some code I wrote to build dynamic menus in WPF with the MVVM pattern and added the checked logic.

Here is the XAML:

<Menu DockPanel.Dock="Top">
    <MenuItem ItemsSource="{Binding Commands}"
              Header="_Item Container Style">
        <MenuItem.ItemContainerStyle>
            <Style TargetType="{x:Type MenuItem}">
                <Setter Property="IsCheckable" Value="True"/>
                <Setter Property="IsChecked"  Value="{Binding Path=Checked}"/>
                <Setter Property="Header" Value="{Binding Path=Text}" />
                <Setter Property="Command" Value="{Binding Path=Command}" />
                <Setter Property="CommandParameter" Value="{Binding Path=Parameter}" />
            </Style>
        </MenuItem.ItemContainerStyle>
    </MenuItem>
</Menu>

Here is the View Model:

public class MainViewModel : ViewModelBase
{
  public MainViewModel()
  {
     GoCommand = new DelegateCommand<object>(OnGoCommand, CanGoCommand);
     LoadCommands();
  }

  private List<MyCommand> _commands = new List<MyCommand>();
  public List<MyCommand> Commands
  {
     get { return _commands; }
  }

  private void LoadCommands()
  {
     MyCommand c1 = new MyCommand { Command = GoCommand, Parameter = "1", Text = "Menu1", Checked = true};
     MyCommand c2 = new MyCommand { Command = GoCommand, Parameter = "2", Text = "Menu2", Checked = true };
     MyCommand c3 = new MyCommand { Command = GoCommand, Parameter = "3", Text = "Menu3", Checked = false };
     MyCommand c4 = new MyCommand { Command = GoCommand, Parameter = "4", Text = "Menu4", Checked = true };
     MyCommand c5 = new MyCommand { Command = GoCommand, Parameter = "5", Text = "Menu5", Checked = false };
     _commands.Add(c1);
     _commands.Add(c2);
     _commands.Add(c3);
     _commands.Add(c4);
     _commands.Add(c5);
  }

  public ICommand GoCommand { get; private set; }
  private void OnGoCommand(object obj)
  {
  }

  private bool CanGoCommand(object obj)
  {
     return true;
  }
}

Here is the class that holds the commands:

  public class MyCommand
  {
     public ICommand Command { get; set; }
     public string Text { get; set; }
     public string Parameter { get; set; }
     public Boolean Checked { get; set; }
  }
Zamboni
The problem with this example is that the number of menu items is static. In my application the `CurrentImage` can change and then I would need to update the number of menu items to match. Also this seems like as much code as just creating the menu items in code with event handling and then handling each menu items `Checked` event and I was hopeful for a XAML solution.
juharr
You could extend my solution to reload the list of commands when the CurrentImage changes, but I agree that is a lot of (simple) code. If you post the code for CurrentImage that might help me and others get a better feel for your question.
Zamboni