I don't understand why I can't detect right click on my list box when I use an MVVM.
I use the event trigger but some events doesn't work.
<ListBox x:Name="PlaylistsList" ItemsSource="{Binding PlaylistsList}" HorizontalAlignment="Left">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseRightButtonDown">
<i:InvokeCommandAction Command="{Binding NewPlaylistCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
My command "NewPlaylistCommand" is never call. Could you help me ?
Thank you.
EDIT:
I found solution to my problem, I used the ContextMenu to interact with my ListBoxItem
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="ListBox">
<TextBlock Text="{Binding Name}"/>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" Command="{Binding RenamePlaylistCommand}"/>
<MenuItem Header="Delete" Command="{Binding DeletePlaylistCommand}" CommandParameter="{Binding SelectedValue, ElementName=PlaylistsList}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Now I can right click on my item to Rename or Delete it.
Thank you for your help.
Most likely that something inside has swallowed your right click event and hence it has not bubbled up (I suspect it has something to do with context menu behaviors). You COULD use the Preview- events which bubble the other way.
The other solution you could use is to directly apply the interaction to each and every child in the list box (and their children as well).
However I honestly don't expect from a UI point of view that right clicking on a play list will create a new play list...I expect from my years of windows usage that a context menu should pop up, possibly with an "add new play list" menuitem.
Related
There is a WPF MVVM app. On the main view I have a list of elements, which are defined with ListView.ItemTemplate, in that I want to have a context menu with Delete action.
The Command for that is separated from the view and is kept in ViewModel DreamListingViewModel.
The problem is that on clicking on Delete I can't get it to execute the command on ViewModelk as context there is that of the item, not the items container.
I can make it work somehow by moving the context menu definition outside of the list view elements, but then when I open the context menu, it flickers, as if it's being called "20" times (which what I think does happen, as many times as I have elements in collection), anyways, I need a clean solution for that and I am very bad with XAML.
Here is how my View looks:
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="0 5 0 5" Background="Transparent" Width="auto">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete"
Command="{Binding DeleteSelectedDream}"
CommandParameter="{Binding DeleteSelectedDream,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type viewmodels:DreamListingViewModel}}}"
/>
</ContextMenu>
</Grid.ContextMenu>
...
It's the main window and initialized in a generic host in App.cs:
public partial class App : Application
{
private readonly IHost _host;
public App()
{
...
_host = Host.CreateDefaultBuilder().ConfigureServices(services =>
{
...
services.AddTransient<DreamListingViewModel>();
services.AddSingleton((s) => new DreamListingView()
{
DataContext = s.GetRequiredService<DreamListingViewModel>()
});
...
}).Build();
The Command and CommandParameter values are what I've been experimenting with, but it doesn't work
Here is how my ViewModel looks:
internal class DreamListingViewModel : ViewModelBase
{
public ICommand DeleteSelectedDream{ get; }
...
Finally, when the command is fired, I need to pass the current element on which the menu has been shown.
So, here is what I want:
User clicks on a list item with mouse right button - OK
Sees a menu with Delete entry - OK
On Delete click, Command DeleteSelectedDream is fired with current dream (item in the list) as a parameter - ERR
Your example is somewhat lacking necessary information, but I'll try to help.
First you need to verify that you are actually bound to your view model. Are you using Prism or just standard WPF ? In the constructor of your code-behind of your view, set up the DataContext to an instance of your VM.
InitializeComponent();
this.DataContext = new DreamListingViewModel();
Now, you bind to a relative source via Mode 'FindAncestor' and the AncestorType is set to the type of a view model. That usually won't work, as the view model is not naturally a part of the visual tree of your WPF view. Maybe your ItemTemplate somehow wires it up. In a large WPF app of mine I use Telerik UI for WPF and a similar approach to you, however, I set up the DataContext of the Context menu to a RelativeSource set to Self combined with Path set to PlacementTarget.DataContext.
You do not have to use all the XAML in my example, just observe how I do it. Exchange 'RadContextMenu' with 'ContextMenu', Ignore the Norwegian words - here and only use what you need :
<telerik:RadContextMenu x:Key="CanceledOperationsViewContextMenu" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext, UpdateSourceTrigger=PropertyChanged}">
<MenuItem Header="{Binding PatientName}" IsEnabled="False" Style="{StaticResource ContextMenuHeading}" />
<MenuItem Header="Gå til aktuell SomeAcme-liste" IsEnabled="{Binding IsValid}" Command="{Binding NavigateToListCommand}" />
<MenuItem Header="Åpne protokoll..." Command="{Binding CommonFirstProtocolCommand, Mode=OneWay}" CommandParameter="{Binding}" />
<MenuItem Header="Åpne Opr.spl.rapport...." Command="{Binding CommonFirstNurseReportCommand, Mode=OneWay}" CommandParameter="{Binding}" />
</telerik:RadContextMenu>
In your example it will be :
<ContextMenu x:Key="SomeContextMenu" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext, UpdateSourceTrigger=PropertyChanged}">
<MenuItem Header="Delete" />
Command="{Binding DeleteSelectedDream}"
CommandParameter="{Binding DeleteSelectedDream,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListViewItem}}}"
/>
</telerik:RadContextMenu>
Now I here consider you are using the class ListViewItem
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.listviewitem?view=netframework-4.8
It might be that you need to specify DataContext.DeleteSelectedDream here to be sure you bind up to the DataContext where your implementation of ICommand is.
Accidentally found this answer, that's basically what I needed, just added to it a CommandParameter to send the item and it works like magic!
<ListView Name="lvDreams" ItemsSource="{Binding Dreams}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="0 5 0 5" Background="Transparent" Width="auto">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem
Header="Delete"
Command="{Binding DataContext.DeleteSelectedDream, Source={x:Reference lvDreams}}"
CommandParameter="{Binding}"
/>
</ContextMenu>
</Grid.ContextMenu>
...
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I find the following the simplest; perhaps it's because I do not understand WPF, but it's "simple" to remember, and it works with my MVVM pattern.
<ListBox ItemsSource="{Binding MyViewModelItemsCollection, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent" >
<TextBlock Text="{Binding Path=Name, Converter={StaticResource FullPathToFileName}, Mode=OneWay}" Grid.Column="0">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem
Command="{Binding Path=DataContext.MyViewModelAction, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"
Header="{Binding Name, Converter={StaticResource resourceFormat}, ConverterParameter={x:Static res:Resources.CONTEXT_MENU_BLOCK_APPLICATION}}">
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</grid>
</DataTemplate>
</ListBox.ItemTemplate
</ListBox>
The MyViewModelXXXXXXX named items are in the view model that is mapped to the data context of the control.
I have a MenuItem with ListView inside. What I want is when I click on a ListView item, some command fires. Here is my code:
<MenuItem Header="?">
<ListView ItemsSource="{Binding CommentTemplateList}" BorderThickness="0" SelectedItem="{Binding SelectedCommentTemplate, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding PasteTemplate}"
CommandParameter="{Binding SelectedCommentTemplate}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Caption}" ToolTip="{Binding Description}" HorizontalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</MenuItem>
Everything is ok, but command PasteTemplate fires only when selection is changed, and I need to it fire every time I click on the item. If I change EventName to one from the list (https://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.selector.aspx), for example MouseDown, the command does not fire at all.
To accomplish this, while respecting the MVVM architecture, the best way is to add the specific behavior to your xaml code as follows;
<ListView x:Name="ListView"
ItemsSource="{x:Bind ViewModel.SampleItems, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedItem, Mode=OneWay}"
IsItemClickEnabled="True">
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="ItemClick">
<ic:InvokeCommandAction Command="{x:Bind ViewModel.ItemClickCommand}" />
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
</ListView>
And in your View Model, after declaring an IComand property as follows,
public ICommand ItemClickCommand
{
get
{
if (_itemClickCommand == null)
{
_itemClickCommand = new RelayCommand<ItemClickEventArgs>(OnItemClick);
}
return _itemClickCommand;
}
}
Define the command as if you were handling the event in the code behind as follows;
private void OnItemClick(ItemClickEventArgs args)
{
ListDataItem item = args?.ClickedItem as ListDataItem;
//DO what ever you want with the Item you selected in the click
}
Note: RelayCommand is used to handled commands using the MVVMLight Framework.
You could handle the PreviewMouseDown event of the ListViewItem as suggested here:
WPF MVVM Light Multiple ListBoxItems bound to same object
<ListView ...>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnMouseLeftButtonDown"/>
</Style>
</ListView.ItemContainerStyle>
..
</ListView>
If you don't want to invoke the command of the view model from the code-behind you could wrap the same functionality in an attached behaviour: https://www.codeproject.com/articles/28959/introduction-to-attached-behaviors-in-wpf.
There is more information an example on the link above.
If you wanna use 'SelectionChanged', You can reset the selection after your code. Just add that on your PasteTemplate
if(((ListView)sender).SelectedIndex == -1)return;
//your code
((ListView)sender).SelectedIndex = -1;
So, after your code, ListView has no selected elements. So if you click it again, the selection is changed again and code fires again.
Note: you can use MouseDown for it too, but it's a little tricky. For example, if user clicks none of your items but somewhere else inside your ListView like this, it fires again with your current selection.
My XAML:
<MenuItem Command="{Binding ShowRequestsCommand}" HorizontalAlignment="Stretch" IsCheckable="True" IsChecked="{Binding ShowUrgentEvaluationRequestNotification, Source={x:Static Properties:Settings.Default}, Mode=TwoWay}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<Border Background="Red" CornerRadius="5" Padding="3,1,3,1" Margin="1,1,5,1" HorizontalAlignment="Left" MinWidth="18"></Border>
<TextBlock>Beställningshanteraren</TextBlock>
</StackPanel>
</MenuItem.Header>
</MenuItem>
So it's a MenuItem with some stuff in the header, it is checkable, and that value is supposed to have some functionality elsewhere, it also has a command.
My problem is that when you click the checkbox, the command is called, which is not my intention, and it seems wrong for any situation honestly. You should be able to click the checkbox without activating whatever the MenuItem is supposed do do. I get the same result if I add a click event on the menuitem instead.
Why does WPF work like this? Is there a workaround?
I have a problem with command binding in WPF. I have the following xaml:
<ItemsControl ItemsSource="{Binding Entity}" Name="Lst">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="qwerty" Command="{Binding ElementName=Lst, Path=DataContext.SaveCommand}" >
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Send2" Command="{Binding ElementName=Lst, Path=DataContext.SaveCommand}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
As you can see Button and its ContextMenu have the similar command-bindings. But when i click button its command is firing and when i click context menu's item its command isn't firing. Where am i wrong? Thanks in advance!
I had a similar problem before and solved it by passing the datacontext through the tag property of the container as below. I have it working on a grid ContextMenu but dont see any reason why this wont work on a button. Let me know if you have any problem
<Button Content="qwerty" Tag="{Binding DataContext,ElementName=Lst}" Command="{Binding ElementName=Lst, Path=DataContext.SaveCommand}" >
<Button.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Send2" Command="{Binding SaveCommand}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
The ContextMenu being separate from the visual tree, you cannot bind with and element outside of it.
If you check your output window, you should have a message saying that it can't find the object "Lst"
A common and easy workaround would be to manually set the DataContext in code-behind (note: this is not breaking MVVM at all. You are just performing a pure UI operation of linking DataContexts together):
In your Xaml:
<Button.ContextMenu>
<ContextMenu Opened="OnContextMenuOpened">
<MenuItem Header="Send2" Command="{Binding ElementName=Lst, Path=DataContext.SaveCommand}" />
</ContextMenu>
</Button.ContextMenu>
In code-behind:
public void OnContextMenuOpened(object sender, RoutedEventArgs args)
{
(sender as ContextMenu).DataContext = Lst.DataContext;
}
You are therefore linking the DataContext every time the ContextMenu is opened (so if Lst's DataContext changes, your ContextMenu will as well)
Alternatively (cleaner if you are bound to use it a lot of times), get the BindingProxy from this article: http://tomlev2.wordpress.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/ and it'll do the trick!
I'm trying to launch an ICommand when the user double-clicks on a listbox item. Also, I'm trying to do this using the MVVM pattern.
In this XAML, the key press "p" works perfectly. When I double click on the list box, the command never starts. I've set a break point to confirm "PlayVideoCommand" is not called with a double-click. Am I missing something or do I have to use Setter (which I'm not familiar with)?
<ListBox Name="SmallVideoPreviews" Grid.Column="1" MaxHeight="965"
ItemsSource="{Binding BrowseVideos}"
ItemTemplate="{StaticResource BrowseTemplate}">
<ListBox.InputBindings>
<KeyBinding Key="p"
Command="{Binding PlayVideoCommand}"
CommandParameter="{Binding ElementName=SmallVideoPreviews, Path=SelectedItem}"/>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding PlayVideoCommand}"
CommandParameter="{Binding ElementName=SmallVideoPreviews, Path=SelectedItem}"/>
</ListBox.InputBindings>
</ListBox>
Both double-click and "p" should execute the same command. When using the mouse, I can see the listboxitem is selected. I have a hunch that the MouseBinding Command property is not a dependency property but I don't know how to confirm this.
What's happening in your sample is that the listbox itself is reacting to the double click, but only in the part of it's area that is not covered by a list box item.
You need the event handler to be tied to the listboxitem.
Some ways to do it are here:
Double Click a ListBox item to open a browser
And some discussion about why a little code-behind in MVVM is not necessarily a terrible thing:
Firing a double click event from a WPF ListView item using MVVM
More discussion:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/9fb566a2-0bd6-48a7-8db3-312cd3e93340/
It seems that the ListBox doesn't handle double click on a ListBoxItem. This is a good answer:
Can't bind Command to ListBox
One could argue weather or not code-behind is terrible, but it ís possible use a command. Add the LeftDoubleClick gesture to the ItemTemplate like this:
<UserControl.Resources>
<DataTemplate x:Key="BrowseTemplate" >
<StackPanel >
<StackPanel.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding DataContext.PlayVideoCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Mode=OneWay}"
CommandParameter="{Binding }" />
</StackPanel.InputBindings>
<TextBlock Text="{Binding }" Width="50" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>