I am implementing a listbox using MVVM approach and I'm having an issue where my double click command is not being triggered. When I launch my application I see my ListBox populated via binding just fine. But when I double click on an item nothing happens. Is there something I'm missing? Many thanks in advance.
Here is how I have my UserControl (xaml) set up
<ListBox
x:Name="Files"
ItemsSource="{Binding FileCollection, Mode=TwoWay}"
SelectedItem="{Binding Filename, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.InputBindings>
<MouseBinding
Gesture="LeftDoubleClick"
Command="{Binding EditFileCommand}"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is how I am setting up my command object in my View Model:
//..using system.windows.input for ICommand
private ICommand editFileCommand = null;
public ICommand EditFileCommand
{
get
{
//RelayCommand comes from GalaSoft.MvvmLight.Command
return editFileCommand ?? new RelayCommand(EditFile);
}
}
private void EditFile()
{
MessageBox.Show("Double Click!");
}
This is almost similar to RelayCommand, you can use it like this:
Declare in your ViewModel:
public RelayCommand EditFileCommand { get; set; }
Then, you need to initialize it:
EditFileCommand = new RelayCommand(EditFile);
The XAML remains equal:
<ListBox
x:Name="Files"
ItemsSource="{Binding FileCollection, Mode=TwoWay}"
SelectedItem="{Binding Filename, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.InputBindings>
<MouseBinding
Gesture="LeftDoubleClick"
Command="{Binding EditFileCommand}"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If the command property is defined in the File class, it should work provided that you actually click on the TextBlock. You could make the TextBlock stretch across the ListBoxItem container by using an ItemContainerStyle:
<ListBox x:Name="Files" ...>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If the command is defined in the view model where the FileCollection property is defined, you should use a RelativeSource:
<TextBlock.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding DataContext.EditFileCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"/>
</TextBlock.InputBindings>
Related
I've got a ListView nested into another Listview. Now I want to bind an double-click event to the ListViewItems of the inner ListView
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="DefaultTemplate">
<ListView Name="jobsView" ItemsSource="{Binding jobs}" SelectedItem="{Binding Path=SelectedProduction}" >
<ListView.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding Path=DataContext.ItemSelectedCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" CommandParameter="{Binding ElementName=jobsView, Path=SelectedItem}" />
</ListView.InputBindings>
</ListView>
</DataTemplate>
</UserControl.Resources>
<ListView Name="weekView" ItemsSource="{Binding dayList}" ItemTemplate="{StaticResource DefaultTemplate}" >
</ListView>
</UserControl>
I created a RelayCommand called ItemSelectedCommand in my ViewModel.
public RelayCommand ItemSelectedCommand { get; private set; }
The RelayCommand is not getting triggered. I guess I'm setting the wrong RelativeSource. How would it look correct?
Where is your ListView inserted. Is there in a Visual Tree a parent with type of UserControl?
Also, what's quite good to fix Binding errors is to take a look at the Console. There should be Binding errors that might point you wherte is the mistake. Usually is writes down where it is trying to search for object and property :)
Also, I am not sure if private get; is actually allowed when binding to property.
<StackPanel Grid.Column="1">
<StackPanel.Resources>
<DataTemplate x:Key="DefaultTemplate" DataType="{x:Type sys:String}">
<StackPanel>
<TextBlock Text="{Binding .}"/>
<ListView>
<ListView.ItemsSource>
<CompositeCollection>
<sys:String>Sub Item</sys:String>
</CompositeCollection>
</ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding DataContext.RenameCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, PresentationTraceSources.TraceLevel=High}" CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</StackPanel.Resources>
<ListView ItemTemplate="{StaticResource DefaultTemplate}">
<ListView.ItemsSource>
<CompositeCollection>
<sys:String> First Item</sys:String>
</CompositeCollection>
</ListView.ItemsSource>
</ListView>
</StackPanel>
Reason why it wasn't working for you is because the double Click was on an actual ListViewItem and NOT the ListView.
What is the situation:
I'm working on a C# WPF application with Caliburn.Micro. I am using the MVVM pattern.
I have a ListView with a ContentControl as ItemTemplate. The ListView's ItemsSource is bound to a List (ObservableCollection) of ViewModels in the corresponding ViewModel.
<ListView ItemsSource="{Binding ViewModelList}" SelectionMode="Extended">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
What I want:
I want to get the selected items (all of them) of the ListView, but I don't know how without violating the MVVM pattern.
It would be nice to have a property "IsSelected" in the ViewModels that are presented by the ContentControl and to bind that somehow to my ListView.
Is it possible to do that or is there another/a better way?
Update:
It was easier than expected. I added a property public bool IsSelected { get; set; } in my ViewModel and put this inside the ListView Control:
<UserControl.Resources>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListViewItem}}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</UserControl.Resources>
Now I can get the selected items with:
foreach (var item in ViewModelList)
{
if (item.IsSelected)
{
// Do stuff
}
}
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneWayToSource}"/>
</Style>
</Grid.Resources>
<ListView ItemsSource="{Binding ViewModelList}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding IsSelected}"/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal">
</StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
This is probably the code you're looking for. If you're using a multiple selection list view, you could get all the selected items with below code.
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectionChanged}" CommandParameter="{Binding Path=SelectedItems, ElementName=ListViewName}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Bind it to a command in the viewmodel.
private ICommand selectionChanged;
public ICommand SelectionChanged
{
get { return selectionChanged; }
set { SetProperty(ref selectionChanged, value); }
}
I have the following user control with a custom dependency property
ThumbnailListView UserControl
<ListView
ItemsSource="{Binding}"
BorderThickness="0,0,0,0"
HorizontalAlignment="Center"
Background="White"
SelectionChanged="ListView_SelectionChanged"
AllowDrop="True">
<i:Interaction.Behaviors>
<behaviors:DragDropBehavior OnDragDrop="{Binding Path=ItemDragDrop}"></behaviors:DragDropBehavior>
</i:Interaction.Behaviors>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource IsLastListItem}}" Value="False">
<Setter Property="Margin" Value="0,0,0,20"></Setter>
</DataTrigger>
</Style.Triggers>
<Setter Property="Background" Value="Gray"></Setter>
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Thumbnail, Converter={StaticResource ImageConverter}}"></Image>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Dependency Property ItemDragDrop of ThumbnailListView
public static ICommand GetItemDragDrop(DependencyObject obj)
{
return (ICommand)obj.GetValue(ItemDragDropProperty);
}
public static void SetItemDragDrop(DependencyObject obj, ICommand value)
{
obj.SetValue(ItemDragDropProperty, value);
}
public static DependencyProperty ItemDragDropProperty = DependencyProperty.RegisterAttached("ItemDragDrop",
typeof(ICommand), typeof(ThumbnailListView));
public ICommand ItemDragDrop
{
get
{
return (ICommand)GetValue(ItemDragDropProperty);
}
set
{
SetValue(ItemDragDropProperty, value);
}
}
NewScansView UserControl
<DockPanel Dock="Top">
<ListView ItemsSource="{Binding Scans}" Width="500">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Caption}" Margin="5,0,0,0"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Views:ThumbnailListView DataContext="{Binding SelectedItem.Pages}" ItemDragDrop="{Binding SelectedItem.DragDropCommand}" VerticalAlignment="Stretch" Width="200" />
<Views:PageListView DataContext="{Binding SelectedItem.Pages}" VerticalAlignment="Stretch" />
</DockPanel>
The NewScansView xaml contains a ThumbnailListView control and has bound the dependency property ItemDragDrop of the ThumbnailListView to a command in the class of SelectedItem.
Inside the ThumbnailListView user control I have a behavior DragDropBehavior which has a dependency property OnDragDrop.
I am trying to bind OnDragDrop to ItemDragDrop so that when a drag drop operation completes the command in the SelectedItem class is executed.
The problem is that it doesn't seem to be able to find either the ItemDragDrop property on the ThumbnailListView or the DragDropCommand of the selected item class.
I'm wondering what i'm doing wrong and how I can set it up?
ThumbnailListView.DataContext is set to SelectedItem.Pages, and I highly doubt that SelectedItem.Pages has a property called SelectedItem.DragDropCommand.
Change the Source of your ItemDragDrop binding to specify it uses something other than the ThumnailListView.DataContext for the source of that binding.
<DockPanel x:Name="MyDockPanel" Dock="Top">
...
<Views:ThumbnailListView DataContext="{Binding SelectedItem.Pages}"
ItemDragDrop="{Binding SelectedItem.DragDropCommand, ElementName=MyDockPanel}" ... />
...
</DockPanel>
You probably also have to do the same for your behavior binding - change the source of it so it points to the ThumbnailListView instead of to the ThumbnailListView.DataContext
<i:Interaction.Behaviors>
<behaviors:DragDropBehavior OnDragDrop="{Binding Path=ItemDragDrop, RelativeSource={RelativeSource AncestorType={x:Type local:ThumbnailListView}}}" />
</i:Interaction.Behaviors>
Or better yet, either make a DependencyProperty for Pages so you don't rely on a specific DataContext object type
<Views:ThumbnailListView PagesDependencyProperty="{Binding SelectedItem.Pages}" ItemDragDrop="{Binding SelectedItem.DragDropCommand}" .. />
Or edit your control so it assumes a specific type is being used for the DataContext, and use an implicit DataTemplate to always draw that type of object using this control (far more common for me) :
<ListView ItemsSource="{Binding Pages}" ...>
<i:Interaction.Behaviors>
<behaviors:DragDropBehavior OnDragDrop="{Binding DragDropCommand}" />
</i:Interaction.Behaviors>
...
</ListView>
Implicit DataTemplate:
<DataTemplate DataType="{x:Type local:WhateverSelectedItemDataTypeIs}}">
<!-- DataContext will automatically set to WhateverSelectedItemDataTypeIs -->
<Views:ThumbnailListView />
</DataTemplate>
I'm converting my project to use MVVM Light.
So far everything worked fine until I got stuck with binding ListViewItem MouseDoubleClick to a command.
Now it looks like that:
<ListView x:Name="ItemsFromStash" Grid.Column="0" Grid.Row="1"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemsSource="{Binding DropBox.DroppedItems}"
ItemTemplate="{DynamicResource DropItemTemplate}"
SelectedItem="{Binding DropBox.SelectedDropItem}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<EventSetter Event="Control.MouseDoubleClick"
Handler="EventSetter_OnHandler"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
I'd like to make it look somewhat like that:
<ListView x:Name="ItemsFromStash" Grid.Column="0" Grid.Row="1"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemsSource="{Binding DropBox.DroppedItems}"
ItemTemplate="{DynamicResource DropItemTemplate}"
SelectedItem="{Binding DropBox.SelectedDropItem}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Custom:EventToCommand Command=
"{Binding DropBox.RenameItemCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
But it says:
Property 'Triggers' is not attachable to elements of type 'Style'
I tried moving the command to ListView.MouseDoubleClick, but than the SelectedItem is null sometimes.
How should do it?
The following code works for me on a listbox, it should be the same:
<ListBox x:Name="listbox_name_here"
ItemsSource="{Binding LastEntries}"
SelectedItem="{Binding SelectedExercise, UpdateSourceTrigger=PropertyChanged}"
MinHeight="150" ToolTip="Double click to edit"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<Command:EventToCommand Command="{Binding your_command_name_here}"
CommandParameter="{Binding ElementName=listbox_name_here,
Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
Do note that the command parameter is using the listbox (listview in your case) name to bind the target for the selected item.
View:
<ListView x:Name="lw" ItemsSource="{Binding DroppedItems}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<Custom:EventToCommand Command="{Binding DataContext.RenameItemCommand, ElementName=lw}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.ItemTemplate >
<DataTemplate >
<Label Content="{Binding Field}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Your command:
private ICommand renameItemCommand;
public ICommand RenameItemCommand
{
get
{
if (renameItemCommand == null)
{
renameItemCommand = new RelayCommand(
param => RenameItem()
);
}
return renameItemCommand;
}
}
private void RenameItem()
{
}
One option could be to create a DataTemplate for the ListView items, and include your EventTrigger there. For example,
<ListView x:Name="ItemsFromStash"
ItemsSource="{Binding DropBox.DroppedItems}" ItemTemplate="{DynamicResource DropItemTemplate}"
SelectedItem="{Binding DropBox.SelectedDropItem}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<Custom:EventToCommand Command="{Binding DropBox.RenameItemCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<!-- Place your template controls here -->
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have the following user control
<ListBox ItemsSource="{Binding Persons}"
SelectedItem="{Binding SelectedPerson}"
VerticalAlignment="Top" Width="166" >
<ListBox.Template>
<ControlTemplate>
<StackPanel >
<ItemsPresenter/>
<Button Content="Add" Background="Transparent" Command="{Binding NewItemCommand}"/>
</StackPanel>
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Height="16" Width="16" Background="Transparent" Command="{Binding DeleteItemCommand}">
<Image Source="images/delete-icon.png" />
</Button>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and i have a view model for it with two commands, the first command that you can see above NewItemCommand is working fine, how ever the second command DeleteItemCommand doesn't function.
is it because its in the item template?
Yes, this is because the DataContext for the ItemTemplate is the Item from Persons not the ViewModel
To bind the DeleteItemCommand on each item you will need to bind back to the ViewModel that is holding the command
Example, binding to the DataContext of the ListBox
<Button Command="{Binding Path=DataContext.DeleteItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}" />
Edit:
If you want to remove the item that the button was clicked on you can pass the item the button belongs to as the CommandParameter and handle that in your comman, Not sure what type of command you are using but this is simple if you are using RelayCommand or DelegateCommand
<Button Command="{Binding Path=DataContext.DeleteItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}"
CommandParameter="{Binding}" />
public MainWindow()
{
InitializeComponent();
DeleteItemCommand = new RelayCommand(person => DeletePerson(person as Person));
}
private void DeletePerson(Person person)
{
Collection.Remove(person);
}