I have a ListView of Expanders. Each Expander (representing a database table) will have items under it, in another ListView. I want to Right-Click and have an "Edit" option on the innermost items, which represent records in the corresponding database table.
There is an ICommand named 'Edit' in my MainEditorViewModel. The Datacontext in which this command resides is the same as that of the outermost ListView named "TestGroupsListView"
Here is the XAML markup for the ListView of Expanders. The outermost ListView I've named for referencing in the binding via ElementName for the MenuItem's Binding:
<ListView Name="TestGroupsListView" ItemsSource="{Binding TestGroups}" Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate>
<Expander Style="{StaticResource MaterialDesignExpander}" >
<Expander.Header>
<Grid MaxHeight="50">
<TextBlock Text="{Binding Name}"/>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Add..." Command="{Binding Add}"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</Expander.Header>
<ListView ItemsSource="{Binding Records}" Style="{StaticResource MaterialDesignListView}" Margin="30 0 0 0">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit"
Command="{Binding ElementName=TestGroupsListView, Path=DataContext.Edit}"
CommandParameter="{Binding }"/>
</ContextMenu>
</Grid.ContextMenu>
<Button Content="{Binding RecordName}" Command="{Binding ElementName=TestGroupsListView, Path=DataContext.Edit}"/>
<!--<TextBlock Text="{Binding RecordName}" AllowDrop="True"/>-->
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I am able to bind a button in the DataTemplate to 'Edit' successfully, but when I attempt to bind the MenuItem's Command to 'Edit', nothing happens. Why might this be that the button command binding works using ElementName but the same binding in the ContextMenu doesn't?
I think it will be better to use the context menu globally for ListView and globally for each Child ListView. Ok, here is my solution:
<ListBox ItemsSource="{Binding Groups}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Add..." Command="{Binding Add}"/>
</ContextMenu>
</ListBox.ContextMenu>
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Name}">
<ListView ItemsSource="{Binding Records}" SelectedItem="{Binding SelectedRecord}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit" Command="{Binding Edit}" IsEnabled="{Binding CanEdit}"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And for better understanding code behind:
public class GroupsVM : ViewModelBase
{
public ICommand Add
{
get => null; //Command implementation
}
public ObservableCollection<GroupVM> Groups { get; set; } = new ObservableCollection<GroupVM>()
{
new GroupVM { Name = "First" },
new GroupVM { Name = "Second" },
new GroupVM { Name = "Third" }
};
}
public class GroupVM : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
public ICommand Edit
{
get => null; //Command implementation
}
public bool CanEdit => SelectedRecord != null;
public ObservableCollection<RecordVM> Records { get; set; } = new ObservableCollection<RecordVM>()
{
new RecordVM { Name="Record1" },
new RecordVM { Name="Record2" },
new RecordVM { Name="Record3" }
};
private RecordVM _selectedRecord = null;
public RecordVM SelectedRecord
{
get => _selectedRecord;
set
{
_selectedRecord = value;
OnPropertyChanged();
OnPropertyChanged("CanEdit");
}
}
}
public class RecordVM : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
}
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>
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.
I have a checkbox inside a listview. The listview is bound to an observable collection. When I use the context menu to try to select all Checkboxes, they do not show as checked. What am I doing wrong?
<ListView Grid.Row="1" Grid.Column="0" ItemsSource="{Binding AvailableModels}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
SelectionMode="Extended">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Select All Models" Command="{Binding
SelectAllModelsAction}" />
<MenuItem Header="Deselect All Models" Command="{Binding
DeselectAllModelsAction}" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding DataContext.IsSelected,
RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type
ListViewItem}}}" VerticalAlignment="Center" />
<Label Content="{Binding Name}" Margin="2,0,0,0" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel
public ObservableCollection<ListItems> AvailableModels
{
get
{
return this.availableModels;
}
set
{
this.availableModels = value;
this.NotifyPropertyChanged(m => m.AvailableModels);
}
}
Context Menu Action
private void SelectAllModels()
{
foreach (var model in this.AvailableModels)
{
model.IsSelected = true;
}
this.NotifyPropertyChanged(m => m.AvailableModels);
}
ListItems Object
public class ListItems
{
public string Name
{
get;
set;
}
public object Value
{
get;
set;
}
public bool IsSelected
{
get;
set;
}
}
ListItems isn't implementing INotifyPropertyChanged so changing IsSelected isn't updating the UI.
Calling NotifyPropertyChanged in SelectAllModels() is unnecessary since the collection itself isn't changed. The NotifyProperyChanged() call in the AvailableModels setter updates the UI when a new collection is set. And ObservableCollection will handle when the collection is modified (items added/removed). However changes to the properties within the ListItems does not update the UI unless they call NotifyPropertyChanged in the setters.
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}"