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
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())
{
}
}
Call to Init method to generate toy menu item's collection DataContext.
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,
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.
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.
Related
I am trying to bind a VM method as a command in MenuItem. Though menu is displays correctly the function never get called.I expecting the MenuCommand Method to be get called from the command binding.
Xaml
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubMenu}">
<TextBlock Text="{Binding Name}">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding MenuCommand}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
ViewModel
public class MenuViewModel : ViewModelBase
{
public ObservableCollection<Menu> Menu { get; set; }
public RelayCommand MenuCommand { get; set; }
public void Load()
{
Menu = new ObservableCollection<Menu> {
new Menu
{
Name = "File",
SubMenu = new List<Menu>
{
new Menu { Name = "New" },
new Menu { Name = "Open" },
new Menu { Name = "Save" }
}
}};
MenuCommand = new RelayCommand(MenuExecution);
}
public void MenuExecution(object item)
{
MessageBox.Show("Hello");
}
}
Thanks #GK & #Mark I am able to bind the command successfully by below Xaml
<Menu ItemsSource="{Binding Menu}" >
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" >
<Setter Property="Command" Value="{Binding ElementName=level1Lister, Path=DataContext.MenuCommand}" />
<Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Self}}"/>
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=SubMenu}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
below is code behind.code for my simple form
In My code behind
public MainWindow()
{
InitializeComponent();
this.DataContext = new viewModel();
}
My view model
public class viewModel
{
public ICommand TaskmenuCommand = new RelayCommand(_OpenTaskWindow);
private static void _OpenTaskWindow(object obj)
{
/*Some logic for Add,Edit delete*/
}
}
Below is my front end.simple list view
<ListView Width="200">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add" Command="{Binding Path= DataContext.TaskmenuCommand, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}}"
CommandParameter="Add"/>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
try like this
<ListView Width="200" VerticalAlignment="Stretch">
<ListView.Resources>
<ContextMenu x:Key="ContextMenuResourceKey">
<MenuItem Header="Add" Command="{Binding Path=DataContext.TaskmenuCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListView}}" />
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}" >
<Setter Property="ContextMenu" Value="{StaticResource ContextMenuResourceKey}" />
</Style>
</ListView.ItemContainerStyle>
<ListViewItem Content="Coffie"></ListViewItem>
<ListViewItem Content="Tea"></ListViewItem>
<ListViewItem Content="Orange Juice"></ListViewItem>
<ListViewItem Content="Milk"></ListViewItem>
<ListViewItem Content="Iced Tea"></ListViewItem>
<ListViewItem Content="Mango Shake"></ListViewItem>
There must be some problem with your RelayCommand. I created my own RelayCommand class as below. My code works with your current XAML.
public class ViewModel
{
public RelayCommand TaskmenuCommand
{ get; set; }
private static void _OpenTaskWindow(object param)
{
MessageBox.Show("ViewModel > " + param.ToString());
}
public ViewModel()
{
TaskmenuCommand = new RelayCommand(_OpenTaskWindow);
}
}
public delegate void CustomFuncDelegate(object parameter);
public class RelayCommand:ICommand
{
public CustomFuncDelegate FuncToExecute { get; set; }
public RelayCommand(CustomFuncDelegate func)
{
FuncToExecute = new CustomFuncDelegate(func);
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
FuncToExecute(parameter);
}
}
Chnage your code as:
<ListView Width="200">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add" Command="{Binding Path= TaskmenuCommand}"
CommandParameter="Add"/>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
You have defined your ContextMenu directly in ListView. It will get the same DataContext in this case, which is usually not the
case with ContextMenus.
I have a TreeListControl that binds to a collection in my VM. I also want to define the context menu inside the treelistcontrol having its header text bind to another string in my VM. how can I set the data context in this case? I tried to
<Window.DataContext>
<model:ViewModel></model:ViewModel>
</Window.DataContext>
<Grid>
<Button Grid.Row="1" Command="{Binding CellCheckedCommand}"></Button>
<TextBlock Text="{Binding HeaderText}" Grid.Row="2">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext}" Header="{Binding HeaderText}"></MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</Grid>
but it doesn't work.
Here is the ViewModel
public DelegateCommand CellCheckedCommand { get; set; }
private String _HeaderText;
public String HeaderText
{
get
{
return _HeaderText;
}
set
{
_HeaderText = value;
NotifyPropertyChanged("HeaderText");
}
}
public void NotifyPropertyChanged(String name)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
private void CellCheckedMethod()
{
HeaderText = "Changed";
}
Provide a name for your window and explicitly bind to it such as
<window x:Name="ReportsPage"/>
...
<MenuItem DataContext="{Binding ElementName=ReportsPage}"/>
UPDATE
Since the context menu is actually in its own window, binding is a bit trickier. Hence the best bet is to walk up the RelativeSource to the context's parent and pull the header text from there:
<Window.DataContext>
<local:MainVM HeaderText="Jabberwocky" />
</Window.DataContext>
...
<TextBlock Text="{Binding HeaderText}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=Parent.DataContext.HeaderText,
RelativeSource={RelativeSource Self}}" />
</ContextMenu>
</TextBlock.ContextMenu>
Which for this context produces this
This binds to a Window:
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
If the command AddItemCommand and property AddItemText are defined on the Window ViewModel, bind to Window DataContext:
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext}"
So I have a context menu for each individual listview item and the listview is bound to a user list. The context menu has a sub-menu that is bound to an observable collection of user statuses. I want to be able to pass the user id from the list view AND the new status ID from the context menu to my update command parameter. I just looked in to MultiBindings and believe this may be a good, long term solution that I can use elsewhere. Here is some code:
ListView from User View:
<ListView Background="Transparent" ItemsSource="{Binding UserList}" SelectionMode="Single">
<ListView.Resources>
<ContextMenu x:Key="Menu" DataContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Name="UserID" Header="{Binding UserID}"/>
<Separator></Separator>
<MenuItem Header="Status" ItemsSource="{Binding DataContext.UserStatus, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"
DisplayMemberPath="Name" Name="StatusID">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.UpdateDriverStatus, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" />
<Setter Property="CommandParameter" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
<MenuItem.CommandParameter>
<MultiBinding Converter="{StaticResource MultiBindConverter}">
<Binding ElementName="DriverID"></Binding>
<Binding ElementName="StatusID"></Binding>
</MultiBinding>
</MenuItem.CommandParameter> </MenuItem>
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource Menu}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<ItemContainerTemplate>
<TextBlock Text="{Binding UserName}" >
</TextBlock>
</ItemContainerTemplate>
</ListView.ItemTemplate>
</ListView>
User VM:
public class UsersPanelVM : ViewModelBase, INotifyPropertyChanged
{
public ObservableCollection<UserPanelItem> UserList { get; set; }
public ObservableCollection<UserStatusList> UserStatus { get; set; }
private readonly IUserService _userService;
public IUserService UserService { get { return this._userService; } }
public UsersPanelVM(IUserService userService)
{
this._userService = userService;
var model = this.UserService.GetUsers();
this.UserList = model.Users;
var statusmodel = this.UserService.GetUserStatus();
this.UserStatus = statusmodel.UserStatus;
this.UpdateUserStatus = new RelayCommand<UserStatusList>((s) => UpdateStatus(1,s));
}
//The 1, above, is hard coded to test the method call, but ideally that should be the selected UserID
private void UpdateStatus(int ID, UserStatusList s)
{
}
public RelayCommand<UserStatusList> UpdateUserStatus { get; private set; }
}
I'm pretty sure I am 100% lost at this point.
This is not necessary as i already mentioned in your other question because you have all the information except the new status in your object model.
Move the command and the UpdateStatus method into the UserPanelItem's class, which also should hold your ID, then you just need to change the command to:
new RelayCommand(param => UpdateStatus(ID, (UserStatusList)param))
If you really want to do it this way: You again set the CommandParameter of the parent MenuItem whose Command will never even be used, move it to the CommandParameter-Setter's Value in the container style, i.e.
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding ...>
....
Assume the following class definitions.
public enum ContentType { Playlist, Audio, Video, Picture }
public interface IDataProvider
{
string Name
{
get;
}
}
public class ProviderList : List<IDataProvider>
{
}
public class MainViewModel
{
public Dictionary<ContentType, ProviderList> ProvidersDictionary;
public MainViewModel()
{
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
}
else
{
// Code runs "for real"
this.ProvidersDictionary = new Dictionary<ContentType, ProviderList>();
ProviderList providerList = new ProviderList();
providerList.Add(new DataProvider());
this.ProvidersDictionary.Add(ContentType.Audio, providerList);
providerList = new ProviderList(providerList);
providerList.Add(new DataProvider());
this.ProvidersDictionary.Add(ContentType.Video, providerList);
}
}
}
So, this ProvidersDictionary property is bound to Window context menu as follows:
<Window.ContextMenu>
<ContextMenu ItemsSource="{Binding ProvidersDictionary}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Value}">
<TextBlock Margin="1" Text="{Binding Key}" Height="20"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Window.ContextMenu>
The question is: how to make ICommand databinding for the DataProvider menu item click and to retrieve data type (enum type) and data provider (IDataProvider interface) within the command' Execute method.
Update
I want to have some command class to be bound to MenuItems like:
class DataProviderMenuSelectCommand : ICommand
{
#region ICommand Members
public void Execute(object parameter)
{
ContentTypeProviderPair contentProviderPair = parameter as ContentTypeProviderPair;
if (contentProviderPair != null)
{
// contentProviderPair.Type property - ContentType
// contentProviderPair.Provider property - IProvider
}
}
}
MainViewModel will expose instance of this command class as a property.
The problem has been solved:
<UserControl x:Class="DataProvidersView"
...
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Path=DataContext.DataProviderSwitchCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type vw:DataProvidersView}}}" />
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding>
<Binding Path="DataContext.Key"
RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}" />
<Binding />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ContextMenu.ItemContainerStyle>
</UserControl>