WPF Binding ContextMenu MenuItem's ItemsSource

别等时光非礼了梦想. 提交于 2019-12-08 07:00:26

问题


I'm trying to bind a single MenuItem's ItemsSource to a ReadOnlyCollection<string>located in the ViewModel. I've read that the ContextMenu is not under the main Visual tree so i can't bind it directly, but any method i try doesn't work. I have the code snippet please let me know what am i doing wrong.

<Window>
…
<DockPanel>
  <!-- Task bar Icon -->
  <tb:TaskbarIcon x:Name="AppNotifyIcon"
       DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
       ToolTipText="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.MainTitle}">
      <tb:TaskbarIcon.ContextMenu>
          <ContextMenu DataContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
                <MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconOpen}" Click="MenuItem_Open_Click"/>
                <MenuItem Header="Technologies" ItemsSource="{Binding to the ReadOnlyCollection of string in ViewModel}">
                   <MenuItem.ItemContainerStyle>
                       <Style>
                          <Setter Property="MenuItem.Command" Value="{Binding <!--Command in ViewModel-->, RelativeSource={RelativeSource AncestorType=Window}}"/>
                          <Setter Property="MenuItem.CommandParameter" Value="{Binding}"/> <!—Binding to the menuItem Header item -->
                       </Style>
                   </MenuItem.ItemContainerStyle>
               </MenuItem>
               <MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconExit}" Click="MenuItem_Exit_Click"/>
          </ContextMenu>
      </tb:TaskbarIcon.ContextMenu>
   </tb:TaskbarIcon>
…
</DockPanel>

I am trying to bind the second MenuItem's ItemsSource and inside it's ItemContainerStyle i want to bind the command and the commandParameter. **Update: ** i'm using hardcodet's TaskbarIcon for wpf, if it matters.

Thanks


回答1:


Try check this out: 1. XAML Code:

        <DataGrid x:Name="SelectDataGrid" 
              ItemsSource="{Binding Persons}" HorizontalAlignment="Left" CellEditEnding="SelectDataGrid_OnCellEditEnding"
              VerticalAlignment="Top" AutoGenerateColumns="False" Loaded="SelectDataGrid_OnLoaded">
        <DataGrid.ContextMenu>
            <ContextMenu>
                <MenuItem Header="Technologies" ItemsSource="{Binding MenuItems}">
                    <MenuItem.ItemContainerStyle>
                        <Style TargetType="MenuItem">
                            <Setter Property="Command" Value="{Binding Command}"/>
                            <Setter Property="Header" Value="{Binding Content}"/>
                            <Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContextMenu}}, Path=DataContext}"/>
                        </Style>
                    </MenuItem.ItemContainerStyle>
                </MenuItem>
            </ContextMenu>
        </DataGrid.ContextMenu>
        <DataGrid.Columns>
            <DataGridTemplateColumn>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Button Command="{Binding HelloCommand}"></Button>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn></DataGrid>

2. DataContext of the context menu is the same as datagrid and window. 3. Inside the DataContext put the next code:

    private void Init()
    {
        MenuItems = new ObservableCollection<MenuItemObject>(new List<MenuItemObject>
        {
            new MenuItemObject {Command = new RelayCommand<object>(Execute), Content = "A"},
            new MenuItemObject {Command = new RelayCommand<object>(Execute), Content = "B"},
            new MenuItemObject {Command = new RelayCommand<object>(Execute), Content = "C"},
            new MenuItemObject {Command = new RelayCommand<object>(Execute), Content = "D"},
        });
    }

    public ObservableCollection<MenuItemObject> MenuItems { get; set; }

    private void Execute(object o)
    {

    }

4. MenuItemsObject model code:

public class MenuItemObject:BaseObservableObject
{
    private ICommand _command;
    private string _content;

    public ICommand Command
    {
        get { return _command; }
        set
        {
            _command = value;
            OnPropertyChanged();
        }
    }

    public string Content
    {
        get { return _content; }
        set
        {
            _content = value;
            OnPropertyChanged();
        }
    }
}

5. MVVM parts implementation:

    public class BaseObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
    {
        var propName = ((MemberExpression)raiser.Body).Member.Name;
        OnPropertyChanged(propName);
    }

    protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
    {
        if (!EqualityComparer<T>.Default.Equals(field, value))
        {
            field = value;
            OnPropertyChanged(name);
            return true;
        }
        return false;
    }
}

public class RelayCommand<T> : ICommand
{
    readonly Action<T> _execute;
    readonly Func<T, bool> _canExecute;

    public event EventHandler CanExecuteChanged;

    public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public void RefreshCommand()
    {
        var cec = CanExecuteChanged;
        if (cec != null)
            cec(this, EventArgs.Empty);
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecute == null) return true;
        return _canExecute((T)parameter);
    }

    public void Execute(object parameter)
    {
        _execute((T)parameter);
    }
}

public class RelayCommand : RelayCommand<object>
{
    public RelayCommand(Action execute, Func<bool> canExecute = null)
        : base(_ => execute(),
            _ => canExecute == null || canExecute())
    {

    }
}
  1. Call to Init method to generate toy menu item's collection DataContext.

  2. An Execute is the method called when some menu item is pressed.

That is all. I'will be glad to help if there will be problems with the code. Regards,




回答2:


Ok, I have found the problem thanks to Ilan's suggestion in the comments of using snoop utility. I saw that in the visual tree, the ContextMenu didn't have its PlacementTarget to point to its parent, the TaskbarIcon (Weird..), but it had an Attached Property called TaskbarIcon.ParentTaskbarIcon from the TaskbarIcon, so i binded the ContextMenu's DataContext to the TaskbarIcon.ParentTaskbarIcon.Tag and that fixed it all.

<Window>
...
<DockPanel>
            <!-- Task bar Icon -->
            <tb:TaskbarIcon x:Name="AppNotifyIcon"
                        IconSource="pack://application:,,,/Icons/HwServerIcon.ico"
                        Tag="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                        ToolTipText="{Binding Tag, RelativeSource={RelativeSource Self}}"><!--{Binding Source={StaticResource LocalizedStrings}, Path=Strings.MainTitle}-->
                <tb:TaskbarIcon.ContextMenu>
                    <ContextMenu DataContext="{Binding Path=(tb:TaskbarIcon.ParentTaskbarIcon).Tag, RelativeSource={RelativeSource Self}}">
                        <MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconOpen}" Click="MenuItem_Open_Click"/>
                        <MenuItem Header="Technologies" ItemsSource="{Binding TechnologiesNames}">
                            <MenuItem.ItemContainerStyle>
                                <Style>
                                    <Setter Property="MenuItem.Command" Value="{Binding DataContext.OpenTechnology, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
                                    <Setter Property="MenuItem.CommandParameter" Value="{Binding}"/>
                                </Style>
                            </MenuItem.ItemContainerStyle>
                        </MenuItem>
                        <MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconExit}" Click="MenuItem_Exit_Click"/>
                    </ContextMenu>
                </tb:TaskbarIcon.ContextMenu>
            </tb:TaskbarIcon>

So, the TaskbarIcon's Tag is pointing the Window's DataContext and the ContextMenu's DataContext is pointing the Taskbar's attached property ParentTaskbarIcon.Tag and from now every binding is performed like it was under the window in the visual tree.




回答3:


For a context menu in a ListBox I add my DataContext to the parent control's tag, and find it in a relative source binding to the placement target. There are many questions on SO regarding this though, and some of those may address more specific instances.

<ListBox ItemsSource="{Binding ItemList}"
         SelectedItem="{Binding SelectedItem}"
         Tag="{Binding}">
                <ListBox.ContextMenu>
                    <ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
                        <MenuItem Header="Delete" 
                                  Command="{Binding Path=DeleteCommand}"/>
                    </ContextMenu>
                </ListBox.ContextMenu>
            </ListBox>

So for your example specifically:

<tb:TaskbarIcon x:Name="AppNotifyIcon"
                DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                ToolTipText="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.MainTitle}"
                Tag="{Binding}">
    <tb:TaskbarIcon.ContextMenu>
        <ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
            <MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconOpen}" Click="MenuItem_Open_Click"/>
            <MenuItem Header="Technologies" ItemsSource="{Binding TechnologyList}">
               <MenuItem.ItemContainerStyle>
                   <Style>
                      <Setter Property="MenuItem.Command" Value="{Binding VmCommand}"/>
                      <Setter Property="MenuItem.CommandParameter" Value="{Binding}"/>
                   </Style>
               </MenuItem.ItemContainerStyle>
           </MenuItem>
           <MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconExit}" Click="MenuItem_Exit_Click"/>
      </ContextMenu>
  </tb:TaskbarIcon.ContextMenu>

Of course your bindings will probably vary, but from here you should at least have the DataContext set, and go from there.



来源:https://stackoverflow.com/questions/33759930/wpf-binding-contextmenu-menuitems-itemssource

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!