Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding ItemsSource and IsChecked of a MenuItem to get a list of checkable items in WPF

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);
        }
    }
}
like image 710
juharr Avatar asked Jun 24 '10 21:06

juharr


2 Answers

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; }
  }
like image 82
Zamboni Avatar answered Sep 28 '22 05:09

Zamboni


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.

like image 35
Quartermeister Avatar answered Sep 28 '22 05:09

Quartermeister