I've been searching for about an hour already and couldn't find a best solution.
I am migrating from VB.NET to C# Forms and to C# WPF.
Never mind that...
so I use this code for C# forms and it works, but not in C# WPF
if (ListView1.SelectedItems.Count > 0)
{
for (lcount = 0; lcount <= ListView1.Items.Count - 1; lcount++)
{
if (ListView1.Items[lcount].Selected == true)
{
var2 = lcount;
break;
}
}
}
this is the way I want to get the index of the item clicked in listbox.
I have the error in .SELECTED
please help.
You can get SelectedIndex from listView. No need to traverse over all items because as per your code you seems to be interested in index of any selected item.
var2 = ListView1.SelectedIndex;
OR
simply this will work if interested in only first index:
if (lst.SelectedItems.Count > 0)
{
var2 = lst.Items.IndexOf(lst.SelectedItems[0]);
}
If you are using the .NET Compact Framework, SelectedIndex is not supported. For a general solution, I prefer SelectedIndices:
ListView.SelectedIndexCollection indices = lst.SelectedIndices;
if (indices.Count > 0)
{
// Do something with indices[0]
}
For Visual Studio 2015, SelectedIndex does not seem to be available. Instead, you can use SelectedIndices[x] where x=0 will give you the first selected item:
listView.SelectedIndices[0]
You can also set the MultipleSelect property to false to only allow one item to be selected at a time.
It can return NULL. Also the SelectedIndexChanged event can be FIRED TWICE. And the first time, there nothing selected yet.
So the only safe way to find it is like this:
private void lv1_SelectedIndexChanged(object sender, EventArgs e)
{
if (lv1.FocusedItem == null) return;
int p = lv1.FocusedItem.Index;
... now int p has the correct value...
sColl.Clear();
string item = String.Empty;
if (listView1.SelectedItems.Count > 0) {
for (int i = 0; i < listView1.SelectedItems.Count; i++) {
if (listView1.SelectedItems[i].Selected) {
int i2 = listView1.SelectedItems[i].Index;
item = listView1.Items[i2].Text;
sColl.Add(item);
}
}
}
listView1.SelectedItems.Clear();
foreach (var itemS in sColl)
{
string items = itemS;
}
sColl.Clear();
listView1.SelectedItems.Clear();
Why don't bring back the SelectedIndex ? Add this extension after your current namespace.
public static class Extension
{
public static int SelectedIndex(this ListView listView)
{
if (listView.SelectedIndices.Count > 0)
return listView.SelectedIndices[0];
else
return 0;
}
}
Encapsulate this class in a namespace called Extensions and then add this inside your projects namespace to use the extension.
using Extensions;
Then simply use like this
private void ListView1_SelectedIndexChanged(object sender, EventArgs e)
{
int selectionindex = ListView1.SelectedIndex();
ListViewItem seletedItem = ListView1.Items[selectionindex];
}
P.S.
The extension method should have returned -1 on Else, but as long as you're using it from the SelectedIndexChanged event, you're fine as it won't get fired if there are no items.
This is by design, as the SelectedIndexChanged event gets fired twice. Once to deselect the initial item, then to select the new one.
Proper way is to return -1 and check for negative numer.
This is also why someone here got and ArgumentOutOfRange Exception.
Related
I'm currently trying to figure out how to get the previous Selected Item in a Combobox, the data is added in a list in the Form1_Load function.
//Flavour Change Button
private void CoboFlav_SelectionChangeCommitted(object sender, EventArgs e)
{
var selectedItemPrice = (coboFlav.SelectedItem as Flavour).price;
var selectedItemName = (coboFlav.SelectedItem as Flavour).name;
var pre_item = pre_selIndex = (coboFlav.SelectedItem as Flavour).price;
//var previousItem = flavourTea_Previous_Var = (coboFlav.SelectedItem as Flavour).price;
//Item List
Flavour listItem1 = ListCopy.MyList2.Find(x => (x.name == "- None -"));
Flavour listItem2 = ListCopy.MyList2.Find(x => (x.name == "Lemon"));
Flavour listItem3 = ListCopy.MyList2.Find(x => (x.name == "Passionfruit"));
Flavour listItem4 = ListCopy.MyList2.Find(x => (x.name == "Yogurt"));
//Checking Base Tea Box for adding price to currentItemTotal
if (coboFlav.Text == listItem1.name || coboFlav.Text == listItem2.name || coboFlav.Text == listItem3.name || coboFlav.Text == listItem4.name)
{
//Increment Item Cost Value & take away previous item cost
currentTotalItemCost += selectedItemPrice - pre_item;
}
//Update CUrrentTotal Text
CurrentTotal.Text = currentTotalItemCost.ToString();
}
If the user selected an option in the combobox the selectedPrice int is increased. I am trying to take away the previousItemCost and im having trouble understanding how to find the previous selected user input.
I am not really sure how to approach this, I have seen a couple of other people declare a new int as -1 and set the SelectedIndex to that. But I don't really understand that solution. If someone could point me in the right direction that would be awesome. Also I am quite new to windows forms as I came from a Unity background.
Thanks
Apparently you want a special kind of ComboBox. In your "object oriented programming" course you learned that if you want a class similar to another class, but with some special behaviour, you should create a derived class:
class PreviousSelectedComboBox : ComboBox // TODO: invent proper name
{
private int previousSelectedIndex = -1;
[System.ComponentModel.Browsable(false)]
public virtual int PreviousSelectedIndex {get; private set;}
{
get => this.previousSelectedIndex;
set => this.previousSelectedIndex = value;
}
[System.ComponentModel.Browsable(false)]
public override int SelectedIndex
{
get => base.SelectedIndex;
set
{
if (this.SelectedIndex != value)
{
this.PreviousSelectedIndex = this.SelectedIndex;
base.SelectedIndex = value;
// TODO: call OnSelectedIndexChanged?
}
}
}
}
Test In a dummy test program or a unit test, check if ComboBox.OnSelectedIndexChanged is called by base.SelectedIndex, If not, call it in the SelectedIndex.Set.
Also check what happens if ComboBox.SelectedItem.Set is called. Does this change the selected index by calling your overridden property SelectedIndex?
Event: I don't think that you need an event PreviousSelectedIndexChanged. It won't add anything, because this event is raised whenever event SelectedIndexChanged is raised, so those who want to get notified when PreviousSelectedIndex changes, could subsbribe to event SelectedIndexChanged.
Still, if you want such an event, follow the pattern that is used in property SelectedIndex and in OnSelectedIndexChanged.
I have a WrapPanel which contains some Images (thumbnails).
When the user press Left or Right arrow key, I want to show the next/previous image.
private void frmMain_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Right)
{
int j = 0;
foreach (Image child in WrapPanelPictures.Children)
{
if (child.Source == MainPic.Source)
{
MainPic.Source = WrapPanelPictures.Children[j + 1].Source;
}
j++;
}
}
}
Also I tried a LINQ approach, but I'm a beginner with LINQ.
var imgfound = from r in WrapPanelPictures.Children.OfType<Image>()
where r.Source == MainPic.Source
select r;
MessageBox.Show(imgfound.Source.ToString());
imgfound is supposed to be a list, right? maybe that's why I can't access Source property. Anyway this return the current Image shown. I want the sibling.
UPDATE:
Well I made a workaround as for now. But still waiting for a proper solution.
I created a ListBox and added all WrapPanel Childrens to it. Then used the SelectedItem and SelectedIndex properties to select the previous and next items.
The WrapPanelPictures.Children[j + 1].Source cannot work since you are trying to access the Source property of UIElement which does not exist. You should cast the UIElement to Image before accessing the Source:
MainPic.Source = (WrapPanelPictures.Children[j + 1] as Image).Source;
I am sure there are more elegant solutions to obtain the same results.
You got your answer why you can't access the Source property by Novitchi. I would nevertheless like to suggest that you rethink your code.
The way I see it, you are in control of what your wrap panel displays. That means you should be able to store things like "all the images" and also the selected index in a field or property. Instead of parsing your wrap panel's children for images every time around and comparing image sources, you should know at any given time what your selected image or index is.
The code might then roughly look something like this:
private List<BitmapImage> _images;
private int _selectedIndex;
private void frmMain_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Right)
{
_selectedIndex = (_selectedIndex + 1) % _images.Count;
MainPic.Source = _images[_selectedIndex];
}
}
If your UI is highly dynamic (drag/dropping images into the wrap panel or something like that), using Bindings to link your data with the UI is the way to go. In any case, I also strongly recommend considering a ViewModel pattern like MVVM.
use method FindVisualChildren. It traverses through Visual Tree and find your desired control.
This should do the trick
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
then you enumerate over the controls like so
foreach (TextBlock tb in FindVisualChildren<TextBlock>(window))
{
// do something with tb here
}
Do not declare j inside the foreach loop. Otherwise this will always show the image for j=0 which is WrapPanelPictures.Children[1]
private void frmMain_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Right)
{
int j = 0;
foreach (Image child in WrapPanelPictures.Children)
{
// int j = 0;
if (child.Source == MainPic.Source)
{
MainPic.Source = WrapPanelPictures.Children[j + 1].Source;
break;
}
j++;
}
}
}
I'm using this snippet to analyze the rows I've selected on a datagrid.
for (int i = 0; i < dgDetalle.Items.Count; i++)
{
DataGridRow row = (DataGridRow)dgDetalle.ItemContainerGenerator.ContainerFromIndex(i);
FrameworkElement cellContent = dgDetalle.Columns[0].GetCellContent(row);
// ... code ...
}
The cycle runs smoothly, but when processing certain indexes, the second line throws a null exception. MSDN's documentation says that ItemContainerGenerator.ContainerFromIndex(i) will return null if 'if the item is not realized', but this doesn't help me to guess how could I get the desired value.
How can I scan all the rows? Is there any other way?
UPDATE
I'm using this snippet to read a CheckBox as explained here. So I can't use binding or ItemSource at all unless I change a lot of things. And I cannot. I'm doing code maintenance.
Try this,
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
grid.UpdateLayout();
grid.ScrollIntoView(grid.Items[index]);
row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
}
The DataGrid is virtualizing the items, the respective rows (i.e. containers) are only created when the row is in view.
You could either turn off virtualization (which makes the first time loading very slow if you have many items, also the memory usage will be higher) or you just iterate over the data and check the values of the data objects' properties which should be bound to the data-grid. Usually you should not need the UI elements at all...
Use this subscription:
TheListBox.ItemContainerGenerator.StatusChanged += (sender, e) =>
{
TheListBox.Dispatcher.Invoke(() =>
{
var TheOne = TheListBox.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem;
if (TheOne != null)
// Use The One
});
};
In addition to other answers: items aren't available in constructor of the control class (page / window / etc).
If you want to access them after created, use Loaded event:
public partial class MyUserControl : UserControl
{
public MyUserControl(int[] values)
{
InitializeComponent();
this.MyItemsControl.ItemsSource = values;
Loaded += (s, e) =>
{
for (int i = 0; i < this.MyItemsControl.Items.Count; ++i)
{
// this.MyItemsControl.ItemContainerGenerator.ContainerFromIndex(i)
}
};
}
}
In my case grid.UpdateLayout(); didn't help an I needed a DoEvents() instead:
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
WPFTools.DoEvents();
grid.ScrollIntoView(grid.Items[index]);
row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
}
/// <summary>
/// WPF DoEvents
/// Source: https://stackoverflow.com/a/11899439/1574221
/// </summary>
public static void DoEvents()
{
var frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(
delegate (object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}), frame);
Dispatcher.PushFrame(frame);
}
I'm trying to avoid having the same code in multiple places. Which event handler would let me to check if I have any items in my ListBox on the fly?
This is how I check if I have any items in ListBox:
if (lbMessage.Items.Count > 0)
{
btnStart.Enabled = true;
}
else
{
btnStart.Enabled = false;
}
There is no event for such an occurrence (for a list of available events, check out the MSDN Documentation for this control). To make your code more re-usable, you could use a property, such as:
public bool ListBoxHasItems
{
get { return lbMessage.Items.Count > 0; }
}
Then you can just call that property each time you want to check if there are any items.
I need to select all items in a ListBox when a CheckBox is clicked. Is it possible to select all items in the ListBox using a single line of code? Or will I have to loop through all items and set selected to true for each one of them?
The fact is that ListBox.Items is a plain object collection and returns plain untyped objects, which cannot be multi-selected (by default).
If you want to multi-select all items, then this will work:
for (int i = 0; i < myListBox.Items.Count;i++)
{
myListBox.SetSelected(i, true);
}
I have seen a number of (similar) answers all which does logically the same thing, and I was baffled why yet they all dont work for me. The key is setting listbox's SelectionMode to SelectionMode.MultiSimple. It doesn't work with SelectionMode.MultiExtended. Considering to select multiple items in a listbox, you will have selection mode set to multiple mode, and mostly people go for the conventional MultiExtended style, this answer should help a lot. And ya not a foreach, but for.
You should actually do this:
lb.SelectionMode = SelectionMode.MultiSimple;
for (int i = 0; i < lb.Items.Count; i++)
lb.SetSelected(i, true);
lb.SelectionMode = //back to what you want
OR
lb.SelectionMode = SelectionMode.MultiSimple;
for (int i = 0; i < lb.Items.Count; i++)
lb.SelectedIndices.Add(i);
lb.SelectionMode = //back to what you want
As far as I can tell, using any of the .NET methods to select a large number of items is far slower than making a direct PInvoke call, passing the LB_SETSEL message (0x185) to the control, with a flag indicating whether you want to Select (1) or Unselect (0) as well as the magic value (-1) which indicates that the change should apply to all items.
[DllImport("user32.dll", EntryPoint = "SendMessage")]
internal static extern IntPtr SendMessage(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam);
// Select All
SendMessage(listBox.Handle, 0x185, (IntPtr)1, (IntPtr)(-1));
// Unselect All
SendMessage(listBox.Handle, 0x185, (IntPtr)0, (IntPtr)(-1));
I think you have to loop here. Selecting all items at once is a pretty specific (and probably rare) use case where it simply makes no sense to offer that functionality out of the box. Furthermore, the loop will be just two lines of code anyway.
I use Mika's solution, however this can be very slow if you have thousands of items. For a massive speed increase you can turn off visibility briefly. The listbox will not actually disappear during the operation as you might suspect, but the selection occurs at least 10x faster in my case.
myListBox.Visible = false;
for (int i = 0; i < myListBox.Items.Count;i++)
{
myListBox.SetSelected(i, true);
}
myListBox.Visible = true;
Within this Constructor, you need to enable the multi selection mode (MultiExtended) of the desired text box.
public Form1()
{
InitializeComponent();
listBox1.SelectionMode = SelectionMode.MultiExtended;
listBox2.SelectionMode = SelectionMode.MultiExtended;
}
After this, use a loop to select everything:
private void selectAll_Click(object sender, EventArgs e)
{
for (int val = 0; val < listBox1.Items.Count; val++)
{
listBox1.SetSelected(val, true);
}
}
I tested it. It works. You can also use the [CTRL/SHIFT] button + left click to select the items individually.
In my case i had 10k+ items, the basic loop method was taking almost a minute to complete. Using #DiogoNeves answer and extending it i wanted to be able to select all (Ctrl+A) & copy (Ctrl+C). i handled this 2 ways. i used the BeginUpdate() and EndUpdate() to defer drawing but i also added a direct copy all (Ctrl+Shift+C) which doesn't even bother to select the items before copying.
private static void HandleListBoxKeyEvents(object sender, KeyEventArgs e)
{
var lb = sender as ListBox;
// if copy
if (e.Control && e.KeyCode == Keys.C)
{
// if shift is also down, copy everything!
var itemstocopy = e.Shift ? lb.Items.Cast<object>() : lb.SelectedItems.Cast<object>();
// build clipboard buffer
var copy_buffer = new StringBuilder();
foreach (object item in itemstocopy)
copy_buffer.AppendLine(item?.ToString());
if (copy_buffer.Length > 0)
Clipboard.SetText(copy_buffer.ToString());
}
// if select all
else if (e.Control && e.KeyCode == Keys.A)
{
lb.BeginUpdate();
for (var i = 0; i < lb.Items.Count; i++)
lb.SetSelected(i, true);
lb.EndUpdate();
}
}
this is absolutely not nice but much faster than a loop if you have many many (100+) items:
Select the Listbox and simulate key input of [home] and [shift]+[end]
lb.BeginUpdate();
lb.Select();
SendKeys.Send("{Home}");
SendKeys.Send("+{End}");
lb.EndUpdate();
EDIT: works with SelectionMode.MultiExtended only I guess
DoubleEDit: also be aware that this might be too slow for code being performed with lb.selecteditems afterwards, but it may be useful for an [Select All] button that the user will click.
Select All is definetly available out of the box:
$("#ListBoxID option").prop("selected", true);
I know this question is tagged with .NET 2.0 but if you have LINQ available to you in 3.5+, you can do the following:
ASP.NET WebForms
var selected = listBox.Items.Cast<System.Web.UI.WebControls.ListItem>().All(i => i.Selected = true);
WinForms
var selected = listBox.SelectedItems.Cast<int>().ToArray();
I added nawfal's idea to what I had already, which was also with 'BeginUpdate'. Additionaly the view position is maintained too, as the user would expect. For me this seems to solve all problems now:
public void SelectAll()
{
bool prevBusy = MouseHelper.IsBusy;
MouseHelper.IsBusy = true;
int topIndex = TopIndex;
// BUG: In 'SelectionMode.MultiExtended' the box gets crazy
SelectionMode previousMode = this.SelectionMode;
this.SelectionMode = SelectionMode.MultiSimple;
this.BeginUpdate();
for (int i = 0; i < Items.Count; i++)
{
SelectedIndices.Add(i);
}
this.EndUpdate();
this.SelectionMode = previousMode;
TopIndex = topIndex;
MouseHelper.IsBusy = prevBusy;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
listbox.SelectAll();
}