Deselect on single click in a ListBox with Extended SelectionMode - c#

I have a ListBox with SelectionMode="Extended". You can only deselect the last item by holding down ctrl while clicking on it. I would like to be able to deselect the item by just clicking on it while not changing the behavior of the Extended selection mode other than that.
I only found one question about this topic and it actually has a different goal (being able to deselect all items by clicking outside of the ListBox).

If I understand your requirement correctly you could handle the PreviewMouseLeftButtonDown event for the ListBoxItem container and de-select it if it's already selected:
<ListBox SelectionMode="Extended">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnMouseLeftButtonDown"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBoxItem>1</ListBoxItem>
<ListBoxItem>2</ListBoxItem>
<ListBoxItem>3</ListBoxItem>
</ListBox>
private void OnMouseLeftButtonDown(object sender, MouseEventArgs e)
{
ListBoxItem lbi = sender as ListBoxItem;
if (lbi != null)
{
if (lbi.IsSelected)
{
lbi.IsSelected = false;
e.Handled = true;
}
}
}
This should allow you to be able to de-select an item without using the CTRL key.

Related

How to create a ContextMenu when the mouse button is pressed

I made a code for a DataGrid that fire the right mouse button event, in particular:
private void Squadre_DataGrid_MouseClick(object sender, MouseEventArgs e)
{
if (e.RightButton == MouseButtonState.Pressed)
{
//Context menu
}
}
I want create a ContextMenu inside the condition, and associate for each item of the ContextMenu a method that will be executed if the item will be choose.
How to do this?
Perhaps you can achieve that in XAML. Assuming you want to have a context menu for the rows of your DataGrid, you can add the ContextMenu property to your DataGridRow, for example:
<DataGrid>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="ContextMenu" Value="{StaticResource theContextMenu}" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
Then add the context menu itself in the resource dictionary:
<Window.Resources>
<ResourceDictionary>
<ContextMenu x:Key="theContextMenu">
<MenuItem Header="Menu Item 1" Click="menuItem1_Click">
</MenuItem>
<MenuItem Header="Menu Item 2" Click="menuItem2_Click">
</MenuItem>
</ContextMenu>
</ResourceDictionary>
</Window.Resources>
Then write a click event handler for each menu item to execute your method:
private void menuItem1_Click(object sender, RoutedEventArgs e)
{
// execute your method..
}
private void menuItem2_Click(object sender, RoutedEventArgs e)
{
// execute your method..
}
You could bind datacontext content to a propertie and than fill It (propertie) in your Button event. Don't forget to set Update condition in Contextmenu binding (xaml)
In my view the best form of work this out is adding an ContextMenu for each row of DataGrid, we can do it in the following way:
In the XAML, place in your DataGrid an listener to event LoadingRow:
<!-- resume version of declaration your DataGrid -->
<DataGrid x:Name="Squadre_DataGrid" LoadingRow="Squadre_DataGrid_LoadingRow" />
In the CodeBehind, come on add the ContextMenu for each row:
private void Squadre_DataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
ContextMenu _contextMenu = new ContextMenu();
MenuItem mia = new MenuItem();//item 1
MenuItem mib = new MenuItem();//item 2
....
_contextMenu.Add(mia);
_contextMenu.Add(mib);
....
e.Row.ContextMenu = _contextMenu;//add context menu to row
}

Why right click event not worked in WPF?

I want to delete my listbox item on right click. But, right click event not worked in my case.
Below is the code which I tried.
In constructor:
listBox1.MouseDown += new MouseButtonEventHandler(listBox1_MouseRightClick);
Right Click:
private void listBox1_MouseRightClick(object sender, MouseButtonEventArgs e)
{
if (sender is ListBoxItem)
{
ListBoxItem item = (ListBoxItem)sender;
Harvest_TimeSheetEntry entryToDelete = (Harvest_TimeSheetEntry)item.DataContext;
MessageBoxResult Result = System.Windows.MessageBox.Show("Are you sure?", "Delete Confirmation", System.Windows.MessageBoxButton.YesNo);
if (Result == MessageBoxResult.Yes)
{
Globals._globalController.harvestManager.deleteHarvestEntry(entryToDelete);
}
else
{
System.Windows.MessageBox.Show("Delete operation Terminated");
}
}
}
In xaml:
<ListBox x:Name="listBox1" ItemsSource="{Binding}" Margin="0,131,0,59" ItemTemplateSelector="{StaticResource templateSelector}" SelectionMode="Single" MouseRightButtonDown="listBox1_MouseRightClick">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseDown" Handler="listBox1_MouseRightClick"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Please suggest, how should I use right click event so it could work?
Your original code seems redundant and verbose. MouseRightButtonDown isn't working because there is already an event handling listbox item selection and the ListBoxItem datacontext is simply the SelectedItem of listBox1.
Get rid of overriding the style and just declare the listbox with the preview event. This will tunnel MouseRightButtonDown instead of bubble it.
<ListBox x:Name="listBox1"
ItemsSource="{Binding}"
ItemTemplateSelector="{StaticResource templateSelector}"
Margin="0,131,0,59"
SelectionMode="Single"
PreviewMouseRightButtonDown="listBox1_MouseRightClick" />
In the constructor, get rid of this
listBox1.MouseDown += new MouseButtonEventHandler(listBox1_MouseRightClick);
Now in the event handler, sender is your listbox1 but if you're not tying this event to other listboxes, simply get the selectedItem from listbox1 and cast it to the appropriate object. Otherwise if you decide you want the functionality on multiple listboxes cast sender to ListBox
private void listBox1_MouseRightClick(object sender, MouseButtonEventArgs e)
{
Harvest_TimeSheetEntry entryToDelete = (Harvest_TimeSheetEntry)listBox1.SelectedItem;
if(entryToDelete != null)
{
//Do work
}
}
Deleting records on right click is not a good design and it leads users make more confuse the functionality. Still if you want to do something, then you can go for the PreviewMouseRightButtonDown event. Please see the below snippet
ListBox1.PreviewMouseRightButtonDown += new MouseButtonEventHandler(ListBox1_MouseRightButtonDown);
Change your XAML as follows
<ListBox x:Name="listBox1"
ItemsSource="{Binding}"
Margin="0,131,0,59"
ItemTemplateSelector="{StaticResource templateSelector}"
SelectionMode="Single">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="ListBox1_PreviewMouseRightButtonDown" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>

How to set focus to a bound ListboxItem by pressing its child element?

I am developping a small WPF application which consist mostly in displaying ObservableCollection<> in others ObservableCollection<>, and so on.
Here is a code example of what my application looks like:
<Listbox Name="MainList" ItemsSource={Binding}>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Textblock Text={Binding MainName} />
<Button>Add item</Button>
<Button>Delete item</Button>
<Listbox Name="ChildList" ItemsSource="{Binding Path=ChildItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock Text={Binding ChildName} />
</DataTemplate>
</ListBox.ItemTemplate>
</Listbox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</Listbox>
And visually it pretty much looks like this:
EDIT:
I will re-explain what I am trying to do.
Whenever I click Button A or Button B I want to Select the MainList ListBoxItem in which they are contained (i.e: A Item)
And in a second time whenever I click Button B:
I want to be sure that a ListBoxItem is selected in ChildList(Second Listbox in the picture)
And if so, I want to delete it in code-behind.
But my main problem is since everything is generated by my bindings I cannot get, so far, an element from my ChildList because ChildList is duplicated in any of my MainList ListBoxItem.
If I understand well the problem is that you want first click on a button of unselected item to select the MainItem, and on next click, when MainItem is already selected, preform click action. Try this when button is clicked:
private ListBoxItem FindItemContainer(DependencyObject obj)
{
while (obj != null && !(obj is ListBoxItem))
{
obj = VisualTreeHelper.GetParent(obj);
}
if (obj != null)
return obj as ListBoxItem;
else
return null;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var lbi = FindItemContainer(sender as DependencyObject);
if (lbi != null)
{
if (lbi.IsSelected)
{
//do click event
}
else
lbi.IsSelected = true;
}
}
Of course you can also do it more MVVM way by binding ListBoxItem.IsSelected to lets say bool MainItem.MyItemIsSelected
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=MyItemIsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
and Button.Command to your ICommand MainItem.DeleteCommand and then when command is executed do something like that:
if (MyItemIsSelected)
{
//do command body
}
else
MyItemIsSelected = true;
which will be better long term because you could replicate SelectedItem behaviour in ChildList object (add MyItemIsSelected and bind it to inner 'ListBoxItem.IsSelected, like discribed above) and add MySelectedItem property to ChildList:
ChildItem MySelectedItem
{
get
{
return Items.FirstOrDefault(n=>n.MyItemIsSelected);
}
}
and your delete command would look like this:
if (MyItemIsSelected)
{
ChildItem selItem = ChildItems.MySelectedItem;
if (selItem != null) ChildItems.Items.Remove(selItem);
}
else
MyItemIsSelected = true;
if everything is data bound and lists are ObservableCollections then you can do all that in object and UI will follow. Actually you can do only this child selection binding bit and still use first solution and in Button_Click look like this:
private void Button_Click(object sender, RoutedEventArgs e)
{
var lbi = FindItemContainer(sender as DependencyObject);
if (lbi != null)
{
if (lbi.IsSelected)
{
MainItem mainItem = lbi.Content as MainItem;
ChildItem selChild = mainItem.ChildItems.MySelectedItem;
if (selChild != null) mainItem.ChildItems.Items.Remove(selChild);
}
else
lbi.IsSelected = true;
}
}
Here is simple, working example on Dropbox
You can do everything you want to do in code behind:
Find the item on which the Button is pressed: in the click-event, cast the sender parameter to type Button. Its DataContext property will contain the item you want to select.
Select the item: set MainList.SelectedItem to the item.
Focus will be on the Button, but that should be ok, since it is inside the item.
Find the selected item in second listbox: locating the ListBox in the DataTemplate is tricky, but you could set its IsSynchronizedWithCurrentItem property to True, and then use the underlying child collection's default CollectionView. You'd find the current item of MainList like above. Then you'd use:
itemToDelete = CollectionViewSource.GetDefaultView(item.ChildItems).CurrentItem;
item.ChildItems.Remove(itemToDelete);

Determining which ListViewItem was clicked on in a ListView when executing a ContextMenu MenuItem

I'm trying to use the context menu in a listview to run some code that requires data from which item it originated from.
I initially just did this:
XAML:
<ListView x:Name="lvResources" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListView.Resources>
<ContextMenu x:Key="resourceContextMenu">
<MenuItem Header="Get Metadata" Name="cmMetadata" Click="cmMetadata_Click" />
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource resourceContextMenu}" />
</Style>
</ListView.ItemContainerStyle>
...
C#:
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
// code that needs item data here
}
But I found that the originating listview item was not accessible that way.
I've read some tactics about how to get around this, like intercepting the MouseDown event and setting a private field to the listviewitem that was clicked, but that doesn't sit well with me as it seems a bit hacky to pass data around that way. And WPF is supposed to be easy, right? :) I've read this SO question and this MSDN forum question, but I'm still not sure how to really do this, as neither of those articles seem to work in my case. Is there a better way to pass the item that was clicked on through to the context menu?
Thanks!
Similar to Charlie's answer, but shouldn't require XAML changes.
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
MenuItem menu = sender as MenuItem;
ListViewItem lvi = lvResources.ItemContainerGenerator.ContainerFromItem(menu.DataContext) as ListViewItem;
}
Well in the cmMetadata_Click handler, you can just query the lvResources.SelectedItem property, since lvResources will be accessible from the code-behind file that the click handler is located in. It's not elegant, but it will work.
If you want to be a little more elegant, you could change where you set up your ContextMenu. For example, you could try something like this:
<ListView x:Name="lvResources" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListView.Style>
<Style TargetType="ListView">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<TextBlock Text="{TemplateBinding Content}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Get Metadata" Name="cmMetadata" Click="cmMetadata_Click"
DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
</ListView.Style>
<ListViewItem>One Item</ListViewItem>
<ListViewItem>Another item</ListViewItem>
</ListView>
What this does is plug in a template for your ListViewItem, and then you can use the handy TemplatedParent shortcut to assign the ListViewItem to the DataContext of your menu item.
Now your code-behind looks like this:
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
MenuItem menu = sender as MenuItem;
ListViewItem item = menu.DataContext as ListViewItem;
}
Obviously the downside is you will now need to complete the template for a ListViewItem, but I'm sure you can find one that will suit your needs pretty quickly.
So I decided to try and implement a command solution. I'm pretty pleased with how it's working now.
First, created my command:
public static class CustomCommands
{
public static RoutedCommand DisplayMetadata = new RoutedCommand();
}
Next in my custom listview control, I added a new command binding to the constructor:
public SortableListView()
{
CommandBindings.Add(new CommandBinding(CustomCommands.DisplayMetadata, DisplayMetadataExecuted, DisplayMetadataCanExecute));
}
And also there, added the event handlers:
public void DisplayMetadataExecuted(object sender, ExecutedRoutedEventArgs e)
{
var nbSelectedItem = (MyItem)e.Parameter;
// do stuff with selected item
}
public void DisplayMetadataCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
I was already using a style selector to dynamically assign styles to the listview items, so instead of doing this in the xaml, I have to set the binding in the codebehind. You could do it in the xaml as well though:
public override Style SelectStyle(object item, DependencyObject container)
{
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(container);
MyItem selectedItem = (MyItem)item;
Style s = new Style();
var listMenuItems = new List<MenuItem>();
var mi = new MenuItem();
mi.Header= "Get Metadata";
mi.Name= "cmMetadata";
mi.Command = CustomCommands.DisplayMetadata;
mi.CommandParameter = selectedItem;
listMenuItems.Add(mi);
ContextMenu cm = new ContextMenu();
cm.ItemsSource = listMenuItems;
// Global styles
s.Setters.Add(new Setter(Control.ContextMenuProperty, cm));
// other style selection code
return s;
}
I like the feel of this solution much better than attempting to set a field on mouse click and try to access what was clicked that way.

listview.selectionchanged, can I make it fire everytime I click an item?

Is there a way to make the selectionchanged event fire every time a selection in the listview is clicked, instead of only when it changes?
For example, lets say i have a listview with only one object in it. The user clicks that object, and that object contains information that populates some textboxes below. The user starts changing some of the values in these textboxes (which are not bound to the object). They then decide that they dont want what is in those text boxes so they'd like to reset everything to what is in the object in the listview. But when they click the one object in the listview, nothing happens, because the selection has not changed.
Hope that makes sense. Anyone know how I can get around this?
The ListView.SelectionChanged and ListViewItem.Selected events are not going to re-fire if the item is already selected. If you need to re-fire it, you could 'deselect' the item when the event fires.
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.AddedItems.OfType<ListViewItem>())
{
Trace.WriteLine("ListViewItem Selected");
item.IsSelected = false;
}
}
Thus allowing you to re-select it ad nauseum. However, if you don't need the actual selection then you should be using an ItemsControl.
If you do want to maintain the select-ability of the item(s) then you should look at registering to a different event than ListView.SelectionChanged, or ListView.Selected. One that works well for this is PreviewMouseDown, as like the initial item selection we want it to occur on both left and right clicks. We could attach it to the single ListViewItem, but since the list may at some point gain more items, we can assign it to all items by using the ItemContainerStyle property of the ListView.
<ListView SelectionChanged="ListView_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<EventSetter Event="PreviewMouseDown"
Handler="ListViewItem_PreviewMouseDown" />
</Style>
</ListView.ItemContainerStyle>
<ListViewItem>Item 1</ListViewItem>
<ListViewItem>Item 2</ListViewItem>
<ListViewItem>Item 3</ListViewItem>
<ListViewItem>Item 4</ListViewItem>
</ListView>
private void ListViewItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Trace.WriteLine("ListViewItem Clicked: " + (sender as ListViewItem).Content);
}
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(ListView.SelectedIndex != -1)
{
//to do staff
}
ListView.SelectedIndex = -1;
}
also we can use this one!
<ListView x:Name="ListView"
Height="Auto" SelectionChanged="ListView_OnSelectionChanged"
Width="260"
Margin="0,-12,0,-25">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding name_to_show_menu,Mode=TwoWay}" Tapped="UIElement_OnTapped"></TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
and in code behind
private void UIElement_OnTapped(object sender, TappedRoutedEventArgs e)
{
//this fire every time
}

Categories

Resources