ListBoxItem index on ListBox mouseover - c#

I have a ListBox in a WPF application that has a MouseMove event handler attached. What I would like to do is to use this event to get the index of the item the mouse is over.
Simplified example of my code:
<StackPanel>
<ListBox x:Name="MyList" MouseMove="OnMouseMove"/>
<Separator/>
<Button>Beep</Button>
</StackPanel>
public CodeBehindConstructor()
{
List<string> list = new List<string>();
list.Add("Hello");
list.Add("World");
list.Add("World"); //Added because my data does have duplicates like this
MyList.ItemsSource = list;
}
public void OnMouseMove(object sender, MouseEventArgs e)
{
//Code to find the item the mouse is over
}

I would try using ViusalHelper HitTest method for that, something like this :
private void listBox_MouseMove(object sender, MouseEventArgs e)
{
var item = VisualTreeHelper.HitTest(listBox, Mouse.GetPosition(listBox)).VisualHit;
// find ListViewItem (or null)
while (item != null && !(item is ListBoxItem))
item = VisualTreeHelper.GetParent(item);
if (item != null)
{
int i = listBox.Items.IndexOf(((ListBoxItem)item).DataContext);
label.Content = string.Format("I'm on item {0}", i);
}
}

Try this:
public void OnMouseMove(object sender, MouseEventArgs e)
{
int currentindex;
var result = sender as ListBoxItem;
for (int i = 0; i < lb.Items.Count; i++)
{
if ((MyList.Items[i] as ListBoxItem).Content.ToString().Equals(result.Content.ToString()))
{
currentindex = i;
break;
}
}
}
You can also try this much shorter option:
public void OnMouseMove(object sender, MouseEventArgs e)
{
int currentindex = MyList.Items.IndexOf(sender) ;
}
However I'm not too sure whether it will work with your method of binding.
Option 3:
A little hacky but you could get the point value of the current location and then use IndexFromPoint
E.g:
public void OnMouseMove(object sender, MouseEventArgs e)
{
//Create a variable to hold the Point value of the current Location
Point pt = new Point(e.Location);
//Retrieve the index of the ListBox item at the current location.
int CurrentItemIndex = lstPosts.IndexFromPoint(pt);
}

Related

SelectedItems.Count exception in listview with VirtualMode

after selecting a value out of my listview and clicking my button i wanted to get my value into the Code but my code is throwing this exception:
Count = 'this.listView1.SelectedItems.Count' threw an exception of type 'System.InvalidOperationException'
private void OK_button_Click(object sender, EventArgs e)
{
try
{
// OK -> Daten übernehmen
ListView.SelectedListViewItemCollection data = this.listView1.SelectedItems;
int iCount = data.Count;
if (iCount != 1)
{
MessageBox.Show("Value is empty");
return;
}
DialogResult = DialogResult.OK;
Close();
}
catch (Exception ex)
{
//WriteProtokoll(ex.ToString(), 0);
Close();
}
}
}
private void listView1_SearchForVirtualItem(object sender, SearchForVirtualItemEventArgs e)
{
e.Index = Array.FindIndex(myData, s => s == textBox1.Text.ToString());
}
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
e.Item = new ListViewItem(myData[e.ItemIndex]);
}
myData = new string[dataListSize];
for (int i = 0; i < dataListSize; i++)
{
myData[i] = String.Format("{0}", i);
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
String MyString = textBox1.Text.ToString();
ListViewItem lvi = listView1.FindItemWithText(MyString.TrimEnd());
//Select the item found and scroll it into view.
if (lvi != null)
{
listView1.SelectedIndices.Clear();
listView1.SelectedIndices.Add(lvi.Index);
listView1.EnsureVisible(lvi.Index);
}
}
This is by design when you use VirutalMode. The documentation states:
In virtual mode, the Items collection is disabled. Attempting to access it results in an InvalidOperationException. The same is true of the CheckedItems collection and the SelectedItems collection.
We can confirm this in the source code.
It goes on to provide the following advice:
If you want to retrieve the selected or checked items, use the SelectedIndices and CheckedIndices collections instead.
You should therefore use this.listView1.SelectedIndices.Count instead.
Looking at the source code again, we can see that this won't throw an exception.

Combobox passed as a Control template then as a popup

I have a combobox "recent_users" as a control template and then as a poppup. How can I pass the selected value of the popup to the method below?(popup click)
recent_users.SelectedItem.ToString() always returns null.
private void usernameEnter_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
Trace.WriteLine("testing dropdown");
ControlTemplate ct = recent_users.Template;
Popup popup1 = ct.FindName("PART_Popup", recent_users) as Popup;
if (popup1 != null)
{
popup1.Placement = PlacementMode.Top;
}
recent_users.IsDropDownOpen = true;
popup1.PreviewMouseUp += new MouseButtonEventHandler((s,e) => popupClick(s,e,recent_users.SelectedItem.ToString()));
}
private void popupClick(object sender, MouseButtonEventArgs e, String recent)
{
usernameEnter.Text = recent;
Trace.WriteLine("appending norms");
}
}
}
Isn't it because the user has not made any selection in the ComboBox? I have re-written the code slightly
private void usernameEnter_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
ControlTemplate ct = recent_users.Template;
Popup popup1 = ct.FindName("PART_Popup", recent_users) as Popup;
if (popup1 != null)
{
popup1.Placement = PlacementMode.Top;
}
recent_users.IsDropDownOpen = true;
// if none selected, set first as selected
if (recent_users.SelectedIndex < 0 && recent_users.HasItems)
{
recent_users.SelectedIndex = 0;
}
var selectedItem = recent_users.SelectedItem as ComboBoxItem;
var selectedUser = selectedItem.Content.ToString();
popup1.PreviewMouseUp += new MouseButtonEventHandler((s, e) => popupClick(s, e, selectedUser));
}
But I was wondering adding a handler for PreviewMouseUp event (in the last line) is something that you really want. I would have fetched the selected user in the SelectionChanged of the recent_users rather.

How to focus ListView's TextCell by button?

I need to focus TextCells one by one via a button click.
I tried listView.ScrollTo.
private void Button_Clicked_1(object sender, EventArgs e)
{
listViewJson.ItemTapped += ListViewJson_ItemTapped;
}
private void ListViewJson_ItemTapped(object sender, ItemTappedEventArgs e)
{
var focusing = e.Item;
listViewJson.ScrollTo(focusing, ScrollToPosition.MakeVisible, true);
}
Firstly, try to define an index to detect which cell to be selected. Then change the index via button click like:
int selectedIndex = 0;
private void MyBtn_Clicked(object sender, EventArgs e)
{
if (selectedIndex == dataList.Count) selectedIndex = 0;
myListView.SelectedItem = dataList[selectedIndex++];
}
when the TextCell is selected the ListView's ItemSelected event will fire, you can put your code in it like:
private void MyListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
//try to do something
}
Here is my code behind for you referring to:
ObservableCollection<string> dataList = new ObservableCollection<string>();
int selectedIndex = 0;
public MainPage()
{
InitializeComponent();
for (int i=0; i<10; i++)
{
dataList.Add("item" + i);
}
myListView.ItemsSource = dataList;
myListView.ItemSelected += MyListView_ItemSelected;
MyBtn.Clicked += MyBtn_Clicked;
}
private void MyListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
//try to do something
}
private void MyBtn_Clicked(object sender, EventArgs e)
{
if (selectedIndex == dataList.Count) selectedIndex = 0;
myListView.SelectedItem = dataList[selectedIndex++];
}

Two Reordering ListBoxes on Same page, prevent user from dragging and dropping between

I have a WPF c# application that has a window with two Reordering Listboxes right next to each other. I used the examples in this link to make the listbox user control. Unfortunately this allows the user to drag from one box and drop in the other. How can I make sure that this doesn't happen?
Here is my code:
public void setItems(List<string> values){
_items = new ObservableCollection<Item>();
foreach (string s in values)
{
_items.Add(new Item(s));
}
listBox.DisplayMemberPath = "Name";
listBox.ItemsSource = _items;
listBox.PreviewMouseMove += ListBox_PreviewMouseMove;
var style = new Style(typeof(ListBoxItem));
style.Setters.Add(new Setter(ListBoxItem.AllowDropProperty, true));
style.Setters.Add(
new EventSetter(
ListBoxItem.PreviewMouseLeftButtonDownEvent,
new MouseButtonEventHandler(ListBoxItem_PreviewMouseLeftButtonDown)));
style.Setters.Add(
new EventSetter(
ListBoxItem.DropEvent,
new DragEventHandler(ListBoxItem_Drop)));
listBox.ItemContainerStyle = style;
}
private void ListBox_PreviewMouseMove(object sender, MouseEventArgs e)
{
Point point = e.GetPosition(null);
Vector diff = _dragStartPoint - point;
if (e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
{
var lb = sender as ListBox;
var lbi = FindVisualParent<ListBoxItem>(((DependencyObject)e.OriginalSource));
if (lbi != null)
{
DragDrop.DoDragDrop(lbi, lbi.DataContext, DragDropEffects.Move);
}
}
}
private void ListBoxItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_dragStartPoint = e.GetPosition(null);
}
private void ListBoxItem_Drop(object sender, DragEventArgs e)
{
if (sender is ListBoxItem)
{
var source = e.Data.GetData(typeof(Item)) as Item;
var target = ((ListBoxItem)(sender)).DataContext as Item;
int sourceIndex = listBox.Items.IndexOf(source);
int targetIndex = listBox.Items.IndexOf(target);
Debug.WriteLine("Target: " + targetIndex.ToString());
Move(source, sourceIndex, targetIndex);
}
}
private void Move(Item source, int sourceIndex, int targetIndex)
{
if (sourceIndex < targetIndex)
{
_items.Insert(targetIndex + 1, source);
_items.RemoveAt(sourceIndex);
}
else
{
int removeIndex = sourceIndex + 1;
if (_items.Count + 1 > removeIndex)
{
_items.Insert(targetIndex, source);
_items.RemoveAt(removeIndex);
}
}
}
}
I figured it out in case anyone else has a similar issue...
I changed the Move function to the following:
private void Move(Item source, int sourceIndex, int targetIndex)
{
IList<Item> prevItems = _items;
try
{
_items.RemoveAt(sourceIndex);
_items.Insert(targetIndex, source);
}
catch(ArgumentOutOfRangeException)
{
//User doesn't need to be notified about this. It just means that they dragged out of the box they were ordering.
//The application does not need to be stopped when this happens.
_items = prevItems;
Debug.WriteLine("User tried to drag between boxes.. Order of boxes were not changed. ");
}
}
I realized that if I remove the item first then I won't have to worry about changing the target or remove index based on the position of the sourceIndex in relation to the targetIndex. Instead I can wrap it in a try catch and look for an ArgumentOutOfRangeException, which will happen if the user tries to drag and drop between. If that happens I reset the box to the previous items.
As far as I can tell this solution works fine... There may have been a simpler way to go about this.

How can I add a context menu to a ListBoxItem?

I have a ListBox and I want to add a context menu to each item in the list. I've seen the "solution" to have the right click select an item and suppress the context menu if on white space, but this solution feels dirty.
Does anyone know a better way?
Just to elaborate a little further to what Frans has said...Even though the ListBox owns the ContextMenuStrip, you can still customize the items in the menu strip at the time it's opening. Thus customizing it's contents based on the mouse position within the listbox.
The example below selects the item in the listbox based on a right mouse click and then customizes a context menu strip based on the item the user right-clicked on. This is a simple example but should get you going: Add a listbox to a form and add this code:
#region Private Members
private ContextMenuStrip listboxContextMenu;
#endregion
private void Form1_Load( object sender, EventArgs e )
{
//assign a contextmenustrip
listboxContextMenu = new ContextMenuStrip();
listboxContextMenu.Opening +=new CancelEventHandler(listboxContextMenu_Opening);
listBox1.ContextMenuStrip = listboxContextMenu;
//load a listbox
for ( int i = 0; i < 100; i++ )
{
listBox1.Items.Add( "Item: " + i );
}
}
private void listBox1_MouseDown( object sender, MouseEventArgs e )
{
if ( e.Button == MouseButtons.Right )
{
//select the item under the mouse pointer
listBox1.SelectedIndex = listBox1.IndexFromPoint( e.Location );
if ( listBox1.SelectedIndex != -1)
{
listboxContextMenu.Show();
}
}
}
private void listboxContextMenu_Opening( object sender, CancelEventArgs e )
{
//clear the menu and add custom items
listboxContextMenu.Items.Clear();
listboxContextMenu.Items.Add( string.Format( "Edit - {0}", listBox1.SelectedItem.ToString() ) );
}
Hope that help.
This way the menu will pop up next to the mouse
private string _selectedMenuItem;
private readonly ContextMenuStrip collectionRoundMenuStrip;
public Form1()
{
var toolStripMenuItem1 = new ToolStripMenuItem {Text = "Copy CR Name"};
toolStripMenuItem1.Click += toolStripMenuItem1_Click;
var toolStripMenuItem2 = new ToolStripMenuItem {Text = "Get information on CR"};
toolStripMenuItem2.Click += toolStripMenuItem2_Click;
collectionRoundMenuStrip = new ContextMenuStrip();
collectionRoundMenuStrip.Items.AddRange(new ToolStripItem[] {toolStripMenuItem1, toolStripMenuItem2 });
listBoxCollectionRounds.MouseDown += listBoxCollectionRounds_MouseDown;
}
private void toolStripMenuItem2_Click(object sender, EventArgs e)
{
var info = GetInfoByName(_selectedMenuItem);
MessageBox.Show(info.Name + Environment.NewLine + info.Date);
}
private void toolStripMenuItem1_Click(object sender, EventArgs e)
{
Clipboard.SetText(_selectedMenuItem);
}
private void myListBox_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Right) return;
var index = myListBox.IndexFromPoint(e.Location);
if (index != ListBox.NoMatches)
{
_selectedMenuItem = listBoxCollectionRounds.Items[index].ToString();
collectionRoundMenuStrip.Show(Cursor.Position);
collectionRoundMenuStrip.Visible = true;
}
else
{
collectionRoundMenuStrip.Visible = false;
}
}
There's no other way: the context menu isn't owned by the item in the listbox but by the listbox itself. It's similar to the treeview control which also owns the context menu instead of the treenode. So whenever an item in the listbox is selected, set the context menu of the listbox according to the selected item.
And Here it is My Solution :
listBox_Usernames.ContextMenuStrip = contextMenuStripRemove;
listBox_Usernames.MouseUp += new MouseEventHandler(listBox_Usernames_MouseUp);
void listBox_Usernames_MouseUp(object sender, MouseEventArgs e)
{
int index = listBox_Usernames.IndexFromPoint(e.Location);
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
if (index != ListBox.NoMatches)
{
if (listBox_Usernames.SelectedIndex == index)
{
listBox_Usernames.ContextMenuStrip.Visible = true;
}
else
{
listBox_Usernames.ContextMenuStrip.Visible = false;
}
}
else
{
listBox_Usernames.ContextMenuStrip.Visible = false;
}
}
else
{
listBox_Usernames.ContextMenuStrip.Visible = false;
}
}
In XAML it shows like this:
<ListBox>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Add User..."/>
<MenuItem Header="Edit..."/>
<MenuItem Header="Disable"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
In one line of code (more for binding):
var datasource = new BindingList<string>( new List<string>( new string[] { "item1" } ) );
listbox.DataSource = datasource ;
listbox.ContextMenu = new ContextMenu(
new MenuItem[] {
new MenuItem("Delete",
new EventHandler( (s,ev) =>
datasource.Remove(listbox.SelectedItem.ToString())
)
)
});
private void buttonAdd_Click(object sender, EventArgs e)
{
datasource.Add( textBox.Text );
}
If its just a question of enabling or disabling context menu items, it might be more efficient to only do it when the context menu is launched rather than every time the list box selection changes:
myListBox.ContextMenu.Popup += new EventHandler(myContextPopupHandler);
private void myContextPopupHandler(Object sender, System.EventArgs e)
{
if (SelectedItem != null)
{
ContextMenu.MenuItems[1].Enabled = true;
ContextMenu.MenuItems[2].Enabled = true;
}
else
{
ContextMenu.MenuItems[1].Enabled = false;
ContextMenu.MenuItems[2].Enabled = false;
}
}
this one is best...
using System.Windows.Forms;
ContextMenuStrip menu;
this.menu.Items.AddRange(new ToolStripItem[] { this.menuItem });
this.listBox.MouseUp += new MouseEventHandler(this.mouse_RightClick);
private void mouse_RightClick(object sender, MouseEventArgs e)
{
int index = this.listBox.IndexFromPoint(e.Location);
if (index != ListBox.NoMatches)
{
menu.Visible = true;
}
else
{
menu.Visible = false;
}
}
//Create and Initialize the contextMenuStrip component
contextMenuStrip_ListaAulas = new ContextMenuStrip();
//Adding an Item
contextMenuStrip_ListaAulas.Items.Add("Modificar");
//Binding the contextMenuStrip with the ListBox
listBox_Aulas.ContextMenuStrip = contextMenuStrip_ListaAulas;
//The solution below
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
//select the item under the mouse pointer
listBox_Aulas.SelectedIndex = listBox_Aulas.IndexFromPoint(e.Location);
//if the selected index is an item, binding the context MenuStrip with the listBox
if (listBox_Aulas.SelectedIndex != -1)
{
listBox_Aulas.ContextMenuStrip = contextMenuStrip_ListaAulas;
}
//else, untie the contextMenuStrip to the listBox
else
{
listBox_Aulas.ContextMenuStrip = null;
}
}
I do like this, this works great and fast for me.
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
{
if (Listbox.SelectedItem == null)
e.Cancel = true;
}

Categories