I had a ListView collection in WPF.
Let's say items were Apple, Banana, Cherry.
Assume user selected Banana at first.
If again user clicks on Banana the item was still selected.
I want to deselect the item on user click, If the same item was already selected.
Set the SelectionMode to Multiple or handle the PreviewMouseLeftButtonDown event for the ListViewItem containers depending on whether you want to be able to select several items or not:
private void ListViewItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ListViewItem lvi = (ListViewItem)sender;
if (lvi.IsSelected)
{
e.Handled = true;
lvi.IsSelected = false;
}
}
XAML:
<ListView xmlns:s="clr-namespace:System;assembly=System.Runtime">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="PreviewMouseLeftButtonDown"
Handler="ListViewItem_PreviewMouseLeftButtonDown" />
</Style>
</ListView.ItemContainerStyle>
<s:String>Apple</s:String>
<s:String>Banana</s:String>
<s:String>Cherry</s:String>
</ListView>
Related
I have an application that has the option to load a custom theme. In that theme, I have a style for ListViewItem that changes the highlight color. In the application I have a GridView that has rows that can be double clicked, and looks like:
<UserControl.Resources>
<Style x:Key="ClickableRowStyle" TargetType="{x:Type ListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="RowDoubleClicked" />
</Style>
</UserControl.Resources>
...
<ListView ItemsSource="{Binding DataItems}" ItemContainerStyle="{StaticResource ClickableRowStyle}">
... Set up GridRows
</ListView>
The problem I am having is that since the grid above uses its own style, the colors from the theme don't get applied.
I have tried adding BasedOn="{StaticResource {x:Type ListViewItem}}". This works if the theme is loaded, however, if the default Windows style is used, I get an exception from the StaticResourceHolder.
Is there a way to get both the look from the theme (if loaded) while still being able to double click the grid rows?
I figured out how to have an optional style and click events at the same time. I set up a loaded event for the list view, and in the loaded event I create a new style using BasedOn if a ListViewItem style already exists.
Xaml:
<ListView x:Name="listView" ItemsSource="{Binding DataItems}" Loaded="ListView_Loaded">
... Set grid rows
</ListView>
C#:
private void ListView_Loaded(object sender RoutedEventArgs e)
{
ListView listView = sender as ListView;
Style style;
if(Application.Current.Resources.Contains(typeof(ListViewItem))
{
style = new Style(typeof(ListViewItem), (Style)Application.Current.Resources[typeof(ListViewItem)]);
}
else
{
style = new Style(typeof(ListViewItem));
}
EventSetter setter = new EventSetter();
setter.Event = ListViewItem.MouseDoubleClickEvent;
setter.Handler = new MouseButtonEventHandler(ListView_MouseDoubleClick);
style.Setters.Add(setter);
listView.ItemContainerStyle = style;
}
The default behaviour of a WPF DataGrid is to select when a row is clicked if SelectionMode="Extended" which is what I want, however I also wish for the row to un-select if it was previously already selected when clicked.
I have tried the following which will unselect the row as soon as it's selected, it seems the row selection occurs before the mouse click event.
private void DoGridMouseLeftButtonUp(object sender, MouseButtonEventArgs args) {
// Get source row.
DependencyObject source = (DependencyObject)args.OriginalSource;
var row = source.FindParent<DataGridRow>();
if (row == null)
return;
// If selected, unselect.
if (row.IsSelected) {
row.IsSelected = false;
args.Handled = true;
}
}
Where I am binding to this event with the following grid.
<DataGrid SelectionMode="Extended"
SelectionUnit="FullRow"
MouseLeftButtonUp="DoGridMouseLeftButtonUp">
I have managed to solve this by instead of handling events on the grid itself to handle them on the cell instead, this involves an event setter for DataGridCell as follows:
<DataGrid SelectionMode="Extended"
SelectionUnit="FullRow">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}">
<EventSetter Event="PreviewMouseLeftButtonDown"
Handler="DoCheckRow"/>
</Style>
</DataGrid.Resources>
<!-- Column mapping omitted. -->
</DataGrid>
Event handler code.
public void DoCheckRow(object sender, MouseButtonEventArgs e) {
DataGridCell cell = sender as DataGridCell;
if (cell != null && !cell.IsEditing) {
DataGridRow row = FindVisualParent<DataGridRow>(cell);
if (row != null) {
row.IsSelected = !row.IsSelected;
e.Handled = true;
}
}
}
My grid is read only so any edit behavior is ignored here.
my wpf datagrid requires CTRL+CLICK for both addition and removal of MULTIPLE rows. so its standard behavior ;) but nevertheless, why you dont use the PreviewMouseDown event and then check for leftmousebutton and Ctrl and do your unselect logic and set e.handled=true?
I have a ListView with ContextMenu on each ListViewItem that has Click event,
how can I detect in the event handler which Item was clicked in this ContextMenu?
I need the item ID.
<Style TargetType="{x:Type ListViewItem}">
.
.
.
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="tv:TreeListViewItem">
<Grid>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Open in current tab" Click="MenuItemCurrentTab_Click"/>
<MenuItem Header="Open in new tab" Click="MenuItemNewTab_Click"/>
</ContextMenu>
</Grid.ContextMenu>
See this thread..
Following the same way as the answer from the link you would
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Open in current tab"
Click="MenuItemCurrentTab_Click"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}"/>
...
private void MenuItemCurrentTab_Click(object sender, RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
if (menuItem != null)
{
ContextMenu parentContextMenu = menuItem.CommandParameter as ContextMenu;
if (parentContextMenu != null)
{
ListViewItem listViewItem = parentContextMenu.PlacementTarget as ListViewItem;
}
}
}
UPDATE
Add this to get the parent ListViewItem from the Grid
public T GetVisualParent<T>(object childObject) where T : Visual
{
DependencyObject child = childObject as DependencyObject;
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
private void MenuItemCurrentTab_Click(object sender, RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
if (menuItem != null)
{
ContextMenu parentContextMenu = menuItem.CommandParameter as ContextMenu;
if (parentContextMenu != null)
{
Grid grid = parentContextMenu.PlacementTarget as Grid;
ListViewItem listViewItem = GetVisualParent<ListViewItem>(grid);
}
}
}
private void MenuItemCurrentTab_Click(object sender, RoutedEventArgs e)
{
MenuItem menuItem = (MenuItem)e.Source;
ContextMenu menu = (ContextMenu)menuItem.Parent;
ListViewItem item = (ListViewItem)menu.PlacementTarget;
// do something with item
}
But it's probably better idea to create single ContextMenu, give it proper name, and use it for all list view items.
A recurring problem, with many attempts to solve but all have their drawbacks. The accepted answer here, for instance, supposes that each ListViewItem has its own ContextMenu. This works but, especially with a larger number of list items, has a considerable cost in XAML complexity and can be slow. And really isn't necessary at all. If we only use a single ContextMenu on the ListView itself, some other solutions suggest to use
<MenuItem CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
which seems to solve the problem at first sight (PlacementTarget points to the ListView, its SelectedItem points to the list item, so the menu item handler can use the CommandParameter to get the originating list item), but, unfortunately, fails if the ListView has multiple selection enabled (SelectedItem will point to one of the items selected but not necessarily the one currently clicked) or if we use ListView.PreviewMouseRightButtonDown to disable the selection on right-click (which is, arguably, the only logical thing to do with multiple selections).
There is, however, an approach that has all the benefits:
single ContextMenu on the ListView itself;
works with all selection schemes, single, multiple, disabled;
even with multiple selection, it will pass the currently hovered item to the handler.
Consider this ListView:
<ListView ContextMenuOpening="ListView_ContextMenuOpening">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Menu1" Click="Menu1_Click" CommandParameter="{Binding Parent, RelativeSource={RelativeSource Self}}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
The CommandParameter is used to pass the parent of the MenuItem, ie. the ContextMenu itself. But the main trick comes in the menu opening handler:
private void ListView_ContextMenuOpening(object sender, ContextMenuEventArgs e) {
var menu = (e.Source as FrameworkElement).ContextMenu;
menu.Tag = (FrameworkElement)e.OriginalSource;
}
Inside this handler, we still know the original source of the event, the root FrameworkElement of the list item DataTemplate. Let's store it in the Tag of the menu for later retrieval.
private void Menu1_Click(object sender, RoutedEventArgs e) {
if (sender is MenuItem menu)
if (menu.CommandParameter is ContextMenu context)
if (context.Tag is FrameworkElement item)
if (item.DataContext is DataType data) {
//process data
}
}
In the menu click handler, we can look up the original ContextMenu we stored in the command parameter, from that we can look up the root FrameworkElement of the list item that we stored just before, and finally get the object stored in the list item (of type DataType).
ListViewItem item = myListView.SelectedItem as ListViewItem;
Seems to work just fine as the item is selected when you right-click it.
I have a ListView in my WPF UserControl using an ItemTemplate to display the items. Within the template is a button. When I select one item and then click on the button of another item, the previously selected item is still selected. I wonder how to automatically select the item the button is in when the button is clicked.
Xaml
<UserControl.Resources>
<DataTemplate x:Key="ItemTemplate">
<Border>
<Grid>
<!-- lots of stuff go here -->
<Button Click="MyButton_Click">Clickme</Button>
</Grid>
</Border>
</DataTemplate>
</UserControl.Resources>
<ListView x:Name="_listView"
ItemTemplate="{StaticResource ItemTemplate}">
</ListView>
C# Code behind
void MyButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show( string.Format( "clicked on {0}",
this._listView.SelectedItem.ToString() ) ) ;
}
I would do it by getting the data context of the sender object. Assuming your listview is a list of objects of type MyObject... then something like this would allow you to reference the selected object.
void MyButton_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
if (b == null)
{
return;
}
MyObject o = b.DataContext as MyObject;
if (o != null)
{
// Put stuff for my object here
}
}
When you press the button your click / mouse down event is handled by the button and therefore does not route through to the ListView control.
A possible way to solve this is to manually set the listview.SelectedItem in the button click event.
How can the handler method of a WPF menu item determine which item in a ListView was clicked on?
Edit:
The menu is a context menu which has been set for the ListView. The problem is to find which ListView item has been clicked on when the context menu item is selected.
Checkout ContextMenu.PlacementTarget, which that object you can walk up the visual tree (VisualTreeHelper.GetParent) until you find a ListViewItem.
Just in case anyone else has this problem, I ended up with something like:
private void ListViewItems_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var frameworkElement = e.OriginalSource as FrameworkElement;
var item = frameworkElement.DataContext as MyDataItem;
if(null == item)
{
return;
}
// TODO: Use item here...
}
If each of your data items has an IsSelected property that is bound to the ListViewItem.IsSelected property, then you just iterate through your data to find the selected ones:
<ListView ItemsSource="{Binding Items}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
And in your code:
public ICollection<DataItem> Items
{
get { return _items; }
}
public IEnumerable<DataItem> SelectedItems
{
get
{
foreach (var item in Items)
{
if (item.IsSelected)
yield return item;
}
}
}
private void DoSomethingWithSelectedItems()
{
foreach (var item in SelectedItems) ...
}