WPF: ContextMenu MenuItem from DataContext/ItemsSource? - wpf

I'm building a simple App to store television shows. I have a Video class for the shows with some fields and properties including one reference to an object of type VideoSeason, representing the seasons of TV shows. The corresponding UI element of a Video object is a Button, with a ContextMenu with some actions.
I would like to create a MenuItem inside the ContextMenu, which contains all the seasons added to a TV show represented as submenuitems. I know that to do this, I have to mark the ObservableCollection Seasons as the ItemsSource of the MenuItem Seasons and indicate that any submenuitem inside the MenuItem is bound to the Property SeasonNumber inside VideoSeason.
My problem is that I dont't really know, how to go about binding these submenuitems in XAML, not if this is actually possible. I've already tried some options (e.g., WPF ContextMenu itemtemplate, menuitem inside menuitem, or Binding WPF ContextMenu MenuItem to UserControl Property vs ViewModel Property), but I want only my MenuItem to be bound, not the whole CntextMenu.
Here is the relevant part of the Video class:
public string Name { get; set; }
public int NextEpisode { get; set; }
public ObservableCollection<VideoSeason> Seasons { get; set; }
And here is the relevant part of the XAML code:
<ScrollViewer>
<StackPanel Name="filmHolder"
Grid.Row="1" Grid.Column="0" >
<ItemsControl Name="VideoUIElment">
<ItemsControl.ItemTemplate>
<DataTemplate x:Uid="videoTemplate">
<Border CornerRadius="10" Padding="10, 10" Background="Silver">
<Button Name="filmLabel" Content="{Binding Name}" FontSize="30" Foreground="Black" VerticalAlignment="Center" HorizontalAlignment="Center"
Click="FilmLabel_Click" BorderThickness="0">
<Button.ContextMenu>
<ContextMenu Name="LocalMenu">
<MenuItem Header="Rename"/>
<MenuItem Header="Delete"/>
<MenuItem Header="Add New Season" Name="NewSeason" Click="NewSeason_Click"/>
<MenuItem Header="Seasons" ItemsSource="{Binding Seasons}">
<!--<MenuItem.ItemTemplate This is one of the things I tried in vain>
<DataTemplate>
<MenuItem Header="{Binding SeasonNumber}"/>
</DataTemplate>
</MenuItem.ItemTemplate>-->
</MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
As it can be seen, the problematic part is nested in the DataTemplate belonging to the UI of the Video, which might be the cause of the problem, but I'm not sure.

If you bind the ItemsSource property of the ItemsControl to an IEnumerable<Video>, this should work:
<ItemsControl Name="VideoUIElment" ItemsSource="{Binding Videos}">
<ItemsControl.ItemTemplate>
<DataTemplate x:Uid="videoTemplate">
<Border CornerRadius="10" Padding="10, 10" Background="Silver">
<Button Name="filmLabel" Content="{Binding Name}" FontSize="30" Foreground="Black" VerticalAlignment="Center" HorizontalAlignment="Center"
Click="FilmLabel_Click" BorderThickness="0">
<Button.ContextMenu>
<ContextMenu Name="LocalMenu">
<MenuItem Header="Rename"/>
<MenuItem Header="Delete"/>
<MenuItem Header="Add New Season" Name="NewSeason" Click="NewSeason_Click"/>
<MenuItem Header="Seasons" ItemsSource="{Binding Seasons}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SeasonNumber}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Make sure that SeasonNumber is a public property of the VideoSeason class.

Related

C# WPF -> context menu bounded command won't be called

I have a little problem with wpf.
I have a listbox and in it a context menu,
but the bounded command won't be called if I am click on a menu item.
I think there is an error with the scope, but I dont have an idia to solve this problem, so I hope, that some guy here can help me.
Here is my code from the ui:
<ListBox Name="CompanyListBox" Margin="5" ItemsSource="{Binding Path=Companys}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border CornerRadius="10" Background="LightGray" Margin="5">
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="Akt. Item in einem neuen Fenster öffnen" Command="{Binding Path=OpenInNewWindowCommand}">
<MenuItem.Icon>
<Image Width="24" Height="24" Source="path/to/icon/icon.ico"></Image>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Border.ContextMenu>
<StackPanel Orientation="Vertical" Margin="5">
//Here is the layout of the item
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And here the code from the viewmodel:
public ICommand OpenInNewWindowCommand
{
get
{
return new RelayCommand(OpenInNewWindow);
}
}
Yes, the DataContext are be setted to the view model, everything works excpet the context menu.
The method "OpenInNewWindow" will be not called from the ui if I clicked on the menu item, why?
(Sorry for bad englisch ;) I´m not from there ^^)

How to display ObserveableCollection<T> in ListBox

I can't bind to my ObservableCollection<T> within my ListBox.
I am using MVVM, WPF.
The binding for the page works. My understanding is, the Grid (not shown in code) is bound to my DataContext, which is my ViewModel. Therefore, my ListBox can bind to my object called Folders via the Itemssource. My Folders object is very simple, it is literally
private ObservableCollection<Folders> _folders;
public ObservableCollection<Folders> Folders
{
get { return _folders; }
set
{
if (value == _folders)
return;
_folders = value;
OnPropertyChanged("Folders");
}
}
and my Folders model is
public class Folders
{
public string SourceFolder { get; set; }
public string DestinationFolder { get; set; }
}
and lastly, my XAML
<ListBox Grid.RowSpan="2" ItemsSource="{Binding Folders, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" SelectedItem="{Binding SelectedFolderItem}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}, AncestorLevel=1}, Path=DataContext}">
<StackPanel>
<TextBlock Text="{Binding SourceFolder}" />
<TextBlock Text="{Binding DestinationFolder}" />
<Button Content="Edit" Command="{Binding EditCommand}" CommandParameter="{Binding SelectedListItem}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The button binds/executes, but the 2 textblocks do not. I have also tried
<TextBlock Text="{Binding Folders.SourceFolder}" />
<TextBlock Text="{Binding Folders.DestinationFolder}" />
but it is the same issue. The content is not displayed (not binding) because if I add a watch on my ViewModel, I can see the values are as they should be.
If it helps, if I update my code to
<TextBlock Text="{Binding SelectedFolderItem.SourceFolder}" />
<TextBlock Text="{Binding SelectedFolderItem.DestinationFolder}" />
then it works although this is not desired (it just loops the correct number of times but only for the 1 item!).
Can some one point me in the right direction?
You are setting a different DataContext. You don't need that, otherwise you destroy the purpose of the item template.
<StackPanel Orientation="Horizontal">
<StackPanel>
<TextBlock Text="{Binding SourceFolder}" />
<TextBlock Text="{Binding DestinationFolder}" />
<Button Content="Edit"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.EditCommand}"
CommandParameter="{Binding}"/>
</StackPanel>
</StackPanel>
of course if you want the buttons to work aswell, you just move the relative source you currently have to the binding of the button.

In WPF, How to get a Command Parameter from a specific item in a Collection View Source that is bound to a ListView?

My Goal: Right click on a specific item in my ListView, a context menu pops up, select a command, followed by me running a function based on which item's context menu was selected.
My ListView's ItemsSource is bounded to a CollectionViewSource, whose source is an ObservableCollection of "Items".
(ListView, binds -> CollectionViewSource, source -> ObservableCollection of class "Item")
What I tried to do is add a general ContextMenu to all of the "Items" in the listview, and when the context menu item is selected for an item in the ListView, then a command is run. I was able to get the command to run in general, but I haven't been able to get any information/parameters about the specific item whose context menu was chosen.
In this example, "Item" class has a string called host, and I want to pass host string to the RefundRequestCommand but I havent been able to pass any CommandParameters.
I've read some stuff about creating a data template and using that, but haven't been successful. Can anyone guide me / help me out?
Here is some code for reference:
ListView:
<ListView x:Name="ordersList" Margin="0,10,10,0" BorderThickness="2" Grid.Column="2" Grid.Row="0" Grid.RowSpan="2" HorizontalAlignment="Stretch" ItemsSource="{Binding Source={StaticResource cvsOrders}}" SelectionChanged="ordersList_SelectionChanged" SelectedIndex="0" SelectionMode="Extended">
<ListView.Resources>
<local:RefundRequestCommand x:Key="refund"></local:RefundRequestCommand>
</ListView.Resources>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="test" Command="{StaticResource refund}" CommandParameter="{Binding host}"></MenuItem>
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn Width="140">
<GridViewColumnHeader Name="OrderNumber" Click="sortClick" Tag="orderNumber" Content="Order Number" />
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding orderNumber}" TextAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
//On and On.....
Command:
class RefundRequestCommand : ICommand
{
TreeViewFilter treeViewFilter;
public void Execute(object parameter)
{
string host = (string)parameter;
Console.WriteLine(host); //FOR TESTING
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
Actually you are setting the ContextMenu for ListView but you want to pass ListViewItem over there. You should set the context menu for ListViewItem. Try this.
<local:RefundRequestCommand x:Key="refund"/>
<Style x:Key="MyLVItemStyle" TargetType="ListViewItem">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="test"
Command="{StaticResource refund}"
CommandParameter="{Binding host}">
</MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
and use it it you listview like
<ListView x:Name="ordersList" Margin="0,10,10,0" BorderThickness="2" Grid.Column="2" Grid.Row="0" Grid.RowSpan="2" HorizontalAlignment="Stretch"
ItemsSource="{Binding Rectangles}" SelectedIndex="0" SelectionMode="Extended" ItemContainerStyle="{StaticResource MyLVItemStyle}">
......
Also you remove the style applied in ListView and it should work.

context menu for removing items in listview

I have a ListView which displays a list of string values. I want to add a context menu entry for each item in the list to remove the selected item. My XAML looks like this:
<ListView x:Name="itemsListView" ItemsSource="{Binding MyItems}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding ElementName=itemsListView, Path=SelectedItem}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
The problem is that the CommandParameter value is always null. I've added an additional button to remove the selected item to check if my command works. The button has exactly the same binding and removing items via the button works. The button looks like this:
<Button Content="Remove selected item"
Command="{Binding RemoveItem}"
CommandParameter="{Binding ElementName=itemsListView, Path=SelectedItem}"/>
The command looks like this:
private ICommand _removeItem;
public ICommand RemoveItem
{
get { return _removeItem ?? (_removeItem = new RelayCommand(p => RemoveItemCommand((string)p))); }
}
private void RemoveItemCommand(string item)
{
if(!string.IsNullOrEmpty(item))
MyItems.Remove(item);
}
Any ideas why the selected item is null when opening the context menu? Maybe a focus problem of the listview?
H.B. is right. but you can also use RelativeSource Binding
<ListView x:Name="itemsListView" ItemsSource="{Binding MyItems}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
ContextMenus are disconnected, you cannot use ElementName bindings. One workaround would be using Binding.Source and x:Reference which requires you to extract parts that use it to be in the resources (due to cyclical dependency errors). You can just put the whole context menu there.
An example:
<ListBox Name="lb" Height="200">
<ListBox.Resources>
<ContextMenu x:Key="cm">
<MenuItem Header="{Binding ActualHeight, Source={x:Reference lb}}" />
</ContextMenu>
</ListBox.Resources>
<ListBox.ContextMenu>
<StaticResource ResourceKey="cm" />
</ListBox.ContextMenu>
</ListBox>
This work for me CommandParameter="{Binding}"

Bind Command for MenuItem in DataTemplate without Tag

I want to bind a command in my ViewModel to a menuItem which is in DataTemplate. I can do that with using Tag. Is there any method which can do the same task but without using tag.
<Window.Resources>
<DataTemplate x:Key="StudentListBoxItemTemplate">
<StackPanel Tag="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}">
<TextBlock Text="{Binding Name}"/>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Trigger" Command="{Binding PlacementTarget.Tag.TriggerCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox
ItemsSource="{Binding StudentList}"
ItemTemplate="{StaticResource StudentListBoxItemTemplate}">
</ListBox>
</StackPanel>
My ViewModel
public class MainViewModel {
public ICommand TriggerCommand { ... }
public ObservableList<Student> StudentList { ... }
}
With your current design, you need to get from the ContextMenu through the StackPanel and back to the DataContext of the containing ListBox. What makes this awkward is that the DataContext of the StackPanel is already narrowed down to a particular student.
There are at least two ways to make this easier:
Provide a TriggerCommand property in Student so the command is right there where you need it
Provide a Parent property in the Student to escape the narrowed scope
You can try to add click event to the menuItem like following
<Menu Style="{StaticResource bellRingersFontStyle}" Height="23" Name="menu1" Width="Auto" DockPanel.Dock="Top" VerticalAlignment="Top">
<MenuItem Header="_File">
<MenuItem Header="_New Member" Name="newMember" Click="newMember_Click" >
<MenuItem.Icon>
<Image Source="Face.bmp" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="_Save Member Details" Name="saveMember" IsEnabled="False" Click="saveMember_Click">
<MenuItem.Icon>
<Image Source="Note.bmp" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="E_xit" Name="exit" Click="exit_Click" />
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="_About Middleshire Bell Ringers" Name="about" Click="about_Click" >
<MenuItem.Icon>
<Image Source="Ring.bmp" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
Try binding the command to Click. My VS is down so can't check at this moment.
One way would be to get the context menu collection presentation defined in your viewmodel, which will contain the header string and command action (maybe with predicate).
The viewmodel creates an observable collection of the contextmenu items and the view binds that to ContextMenu itemssource and sets the displaymember path to header string.

Resources