TextBox.TextChange to update an onscreen ListBox in C# - c#

Now this might not sound horribly cryptic, but I'm a little new to handling on-screen elements and Forms and such in C# so bear with me here. It's also going to be a bit of a long one, as I feel I should provide as much information as possible.
I have a TextBox object, which is added to my fSelect (which is my Form in this case), and it's done like this:
TextBox searchBox = new TextBox();
fSelect.Controls.Add(searchBox);
searchBox.Location = new Point(40, 255);
searchBox.Width = 520;
searchBox.TextChanged += new EventHandler(searchBox_TextChanged);
Now, as you can see every time something changes in the TextBox a certain operation is carried out. That operation would be:
private void searchBox_TextChanged(object sender, EventArgs e)
{
TextBox s = (TextBox)sender;
bool b = false;
List<string> f = new List<string>();
ListBox updatedLb = new ListBox();
updatedLb.Size = new System.Drawing.Size(568, 255);
updatedLb.SelectionMode = SelectionMode.MultiSimple;
foreach (string value in lb.Items)
{
if (value.IndexOf(s.Text, StringComparison.OrdinalIgnoreCase) >= 0)
{
f.Add(value);
b = true;
}
}
for (int i = 0; i < f.Count; i++)
{
updatedLb.Items.Add(f[i]);
}
s.FindForm().Controls.Remove(lb);
s.FindForm().Controls.Add(updatedLb);
}
Looking at that code, there's one element that needs explanation, lb. lb is in this case a public ListBox which contains a set amount of string elements. It is defined as a ListBox outside any method.
Now, in the method my searchBox was defined I filled my lb as follows:
foreach (string value in list)
{
lb.Items.Add(value.title);
}
(If it matters, I should also mention that the adding of strings to lb happens before it is inserted into the fSelect Form later.)
Now, for those who haven't guessed my question yet; I wish to present a user with a Form that has a ListBox on it. The elements in this ListBox will be whatever elements contain the string the user types into searchBox, AS the user types it.
The problem is, that the search is only carried out once, and the only thing that is searched for is the first key the user puts in the searchBox. IE: If I was the user and I was going to search for "key", and the first letter was "k", the list would update to show anything with "k" in the title. But when I tried to type the "e" it would not update or change. It would also not go revert back to the old list if the user removes part of the text in searchBox.
How do I go about getting a Form that shows me a ListBox with elements based on what a user entered into searchBox?

The problem is that you are looping though lb, which you delete the first time the text_Changed-Event triggers. So you don't have any items more that you can loop through and filter your ListBox. The easiest way to fix that would be not getting the items you loop though from the ListBox itself, but from a List<string> that you use to supply the ListBox.
Edit
Here's the way I'd do it:
In your form class you add a List<string>, this list is the base for your ListBox.
List<string> listBoxItems = new List<string>();
Now you can fill this list in your constructor with the items you want. Here are some ways to do it. For my example I'll just add all the items manually for simplicity.
listBoxItems.Add("Item1");
listBoxItems.Add("abc");
listBoxItems.Add("CDE");
listBoxItems.Add("Abra");
Then you want to fill your ListBox with the items from your List.
foreach (var s in listBoxItems)
{
listBox1.Items.Add(s);
}
Now you just need to handle the Text_Changed-Event. Instead of creating a new ListBox every time, you just edit the one we already have in the form and instead of looping though the items in the ListBox, which are already filtered, you loop though all the item saved in your List<string>.
private void textBox1_TextChanged(object sender, EventArgs e)
{
TextBox s = (TextBox)sender;
listBox1.Items.Clear();
foreach (string value in listBoxItems)
{
if (value.IndexOf(s.Text, StringComparison.OrdinalIgnoreCase) >= 0)
{
listBox1.Items.Add(value);
}
}
}
In your code, you created a new ´ListBox´ each time you go into the loop, but you still try to use the items from your old one.
I hope this helps,
Dominik

Related

Is there any way to link a several Textbox to a list on Window Form App C#?

I'm trying to create a window form app with arround 20 textboxes, named "TB1", "TB2",....
The problems is, if I have to work with 20 individual textbox, it might be really annoying.
Is there any way to create a list like: TB[], then assign TB1 to TB[0], TB2 to TB[1],...?
Thank you.
They're already in a list; they have to be added to a ControlCollection in order to even show up on a form
this.Controls.OfType<TextBox>().Where(tb => tb.Name.StartsWith("TB"))
will, for example, give you them (it picks on all textboxes directly on the form, with a name that starts TB. If you have other textboxes also called TBx that you don't want in this, then pick in another property that identifies the ones you do want, or e.g. rename the controls you're interested in so you can pick them up by a pattern in the name)
You could put this in a class level list, in the constructor:
class YourForm: Form{
private List<TextBox> _tbList;
YourForm(){
InitializeComponent();
tbList = Controls.OfType<TextBox>().Where(tb => tb.Name.StartsWith("TB")).ToList();
}
}
And then you could do things like clearing all of them:
_tbList.ForEach(tb => tb.Clear());
List is used to give access to ForEach; it's a List thing, not a LINQ thing - normal arrays don't have ForEach. If you use an array rather than a list you'd need to write eg foreach(var tb in _tbArray) tb.Clear(); but it's the same overall effect
Note, if your textboxes are in panels or group boxes, they won't be in this.Controls they will be in thePanel.Controls
Note2, if the order matters then you can sort strings of TB1, TB2 .. TB9, TB10, ... correctly with:
Controls.OfType<TextBox>()
.Where(tb => tb.Name.StartsWith("TB"))
.OrderBy(tb => (tb.Name.Length, tb.Name))
An alternative to using the controls collection. The trick is hooking them all up to one event handler:
private TextBox[] TB = new TextBox[19];
public void Form_Load(object sender, EventArgs e){
for (int i = 0 ; i < 20; i++)
{
TB[i].Name = "TB" + i.ToString();
// Set Location, Visible & Text
TB[i].Changed += TextBox_Changed;
}
}
private void TextBox_Changed(object sender, EventArgs e)
{
TextBox txt = sender as TextBox;
int i = Convert.ToInt32(txt.Name.Replace("TB", string.Empty));
Console.Write(TB[i].Text + " has changed");
}

Is it wrong to set ListBox DataSource property to null in order to change list Items?

I found that Items.Clear does not always clear a listbox when the listbox has been filled via a DataSource. Setting the DataSource to Null allows it to be cleared with Items.Clear().
Is this the wrong way to do it this way? Is my thinking a bit wrong to do this?
Thanks.
Below is the code I prepared to illustrate my problem. It includes one Listbox and three buttons.
If you click the buttons in this order everything Everything works:
Fill List With Array button
Fill List Items With Array button
Fill List Items With DataSource button
But if you click the "Fill List Items With DataSource" button first, clicking on either of the other two buttons causes this error: "An unhandled exception of type 'System.ArgumentException' occurred in System.Windows.Forms.dll" with "Items collection cannot be modified when the DataSource property is set."
Comments?
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnFillListWithArray_Click(object sender, EventArgs e)
{
string[] myList = new string[4];
myList[0] = "One";
myList[1] = "Two";
myList[2] = "Three";
myList[3] = "Four";
//listBox1.DataSource = null; <= required to clear list
listBox1.Items.Clear();
listBox1.Items.AddRange(myList);
}
private void btnFillListItemsWithList_Click(object sender, EventArgs e)
{
List<string> LStrings = new List<string> { "Lorem", "ipsum", "dolor", "sit" };
//listBox1.DataSource = null; <= required to clear list
listBox1.Items.Clear();
listBox1.Items.AddRange(LStrings.ToArray());
}
private void btnFillListItemsWithDataSource_Click(object sender, EventArgs e)
{
List<string> LWords = new List<string> { "Alpha", "Beta", "Gamma", "Delta" };
//listBox1.DataSource = null; <= required to clear list
listBox1.Items.Clear();
listBox1.DataSource = LWords;
}
}
According to Microsoft it looks like setting the Datasource to Null then Clearing the list is acceptable.
Source: http://support.microsoft.com/kb/319927
If your listbox is bound to a datasource, then that datasource becomes the 'master' of the listbox. You then don't clear the listbox, but you need to clear the datasource.
So if the listbox is bound to LWords, you do Lwords.clear() and the listbox would be cleared.
And that is correct behaviour, because that is what being databound is all about.
If you set the datasource to null, you are basically telling the listbox that it is no longer databound. And of course as a side effect of that it becomes empty.
But depending on the situation you might not want the listbox just to be cleared, but you might want to clear the datasource and the listbox both.
Suppose you want to clear LWords via your GUI, and that LWords is the source of your listbox, you press a button and you set the datasource to null, you see the listbox becoming empty, thinking that LWords is not empty, but LWords is not empty at all, and then in this situation that would be a bug.

How can I programmatically select specified ListBox items?

I've got this code that populates a ListBox when a flyout is opened:
private void flyoutOpenPhotosets_Opened(object sender, object e)
{
lstbxPhotosets.ItemsSource = PhotraxSQLiteUtils.GetPhotosets();
foreach (String pset in App.CurrentlyMappedPhotosets)
{
int lstbxIndex = lstbxPhotosets.Items.IndexOf(pset);
if (lstbxIndex >= 0)
{
lstbxPhotosets.Items[lstbxIndex].? what now?
}
}
}
GetPhotosets returns a List. That part works (the list box is populated with the appropriate string values)
The problem is with the rest of the code (the foreach block).
CurrentlyMappedPhotosets is also a List. I want matching members among the strings in CurrentlyMappedPhotosets and those in the ListBox to cause the item in the ListBox to be selected when the flyout displays.
I was hoping to do be able to do something like this:
lstbxPhotosets.Items[lstbxIndex].Selected = true;
...but lstbxPhotosets is disallowing that.
So how can I programmatically select specified ListBox items?
Use
lstbxPhotosets.SelectedIndex = lstbxIndex

Disposing of list and listbox items

Hey I have a really annoying problem I have a list of doubles and a list box. A user can enter a value into a textbox and then click EnterValue.
List<double> listDouble = new List<double>();
private void EnterValue_Click(object sender, EventArgs e)
{
//convert input to double
listDouble.Add(Convert.ToDouble(textBox1.Text));
textBox1.Clear();
//clear existing items
listBox1.Items.Clear();
// clear any existing list items
for (int i = 0; i < listDouble.Count; i++)
{
listBox1.Items.Add(listDouble[i]);
}
}
I then have another button the user can click which will sum that list of doubles entered.
private void button2_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
CalculateSumOfList.ServiceReference1.Service1SoapClient client = new CalculateSumOfList.ServiceReference1.Service1SoapClient();
CalculateSumOfList.ServiceReference1.ArrayOfDouble arrayOfDoubles = new CalculateSumOfList.ServiceReference1.ArrayOfDouble();
arrayOfDoubles.AddRange(listDouble);
string result = client.CalculateSum(arrayOfDoubles);
label1.Text = result;
//for (int i = 0; i < listDouble.Count; i++)
//{
// listDouble.Remove(i);
//}
}
The problem here is that when the user enters a value it is added to the list which is fine, when then user enters another number the list displays fine, and when the user calculates the sum of the list the value is correct. But after that if the user then goes back to add more numbers to the list the "old" list is appended onto the end?
Ive tryed every means to dispose of that "old" list after the calculation has been done. But nothing works. Which ofcourse then ruins the second attempt.
Use listDouble.Clear() to completely clear the list.
Your loop (commented out in your code example) isn't working correctly. Just think it through:
First you remove the first element which makes the second element the new first element.
Now you remove the new second element (previously that was the third element). The original second element never gets removed.
This continues until half of the elements are removed ...

How to update ListView's selected item?

I have a ListView which displays multiple rows of ListViewItems. The user is able to edit one of these items through a dialog which opens after clicking 'Edit.' When the dialog closes I would like to modify the selected ListViewItem such that it reflects the new settings.
Here is how I currently update my item:
private void btnEditSnmpV3Setting_Click(object sender, EventArgs e)
{
if (lstVwSNMPv3Settings.SelectedItems.Count > 0)
{
ListViewItem selectedItem = lstVwSNMPv3Settings.SelectedItems[0];
NetworkDiscoverySnmpSetting settings = (NetworkDiscoverySnmpSetting)selectedItem.Tag;
NetworkDiscoverySnmpV3SettingsDialog dialog = new NetworkDiscoverySnmpV3SettingsDialog(settings);
//Pass in the owner for centering of dialog.
if (dialog.ShowDialog(this) == DialogResult.OK)
{
selectedItem.SubItems.Clear();
selectedItem.Text = settings.SnmpV3Username;
selectedItem.SubItems.Add(settings.SecurityMode.ToString());
selectedItem.SubItems.Add(settings.AuthenticationProtocol.ToString());
selectedItem.SubItems.Add(settings.PrivacyProtocol.ToString());
selectedItem.Tag = settings;
}
}
}
I found this to be a poor solution due to the fact that I need to touch code in multiple places if my ListView's number of columns changes.
I handled this code-reuse issue during the 'Add' event (as opposed to 'Edit') by giving NetworkDiscoverySnmpSetting a utility method:
public ListViewItem ToListViewItem()
{
ListViewItem listViewItem = new ListViewItem();
listViewItem.Text = SnmpV3Username;
listViewItem.SubItems.Add(SecurityMode.ToString());
listViewItem.SubItems.Add(AuthenticationProtocol.ToString());
listViewItem.SubItems.Add(PrivacyProtocol.ToString());
listViewItem.Tag = this;
return listViewItem;
}
which is used like so:
private void btnAddSnmpV3Setting_Click(object sender, EventArgs e)
{
NetworkDiscoverySnmpSetting settings = new NetworkDiscoverySnmpSetting(NetworkDiscovery.ID);
NetworkDiscoverySnmpV3SettingsDialog dialog = new NetworkDiscoverySnmpV3SettingsDialog(settings);
//Pass in the owner for centering of dialog.
if (dialog.ShowDialog(this) == DialogResult.OK)
lstVwSNMPv3Settings.Items.Add(settings.ToListViewItem());
}
Unfortunately, ListView.SelectedItems does not allow collection-modification. As such, this does not compile:
lstVwSNMPv3Settings.SelectedItems[0] = settings.ToListViewItem();
How should I change my first code-snippet so that I do not need to update my code in multiple places when ListView's columns change?
You can modify the element itself rather than replacing it with another one, because ListViewItem is a class, so it's a reference type.
In order to do this follow these steps:
get currently selected item and save it to variable like this: ListViewItem selectedItem = lstVwSNMPv3Settings.SelectedItems[0];
modify your ToListViewItem method to void ToListViewItem(ListViewItem listViewItem) (return void and take ListViewItem object as parameter and modify it instead of creating a new object. It should also rather modify properties of existing subitems than creating new ones. It can look more or less like this:
public void ToListViewItem(ListViewItem listViewItem)
{
listViewItem.Text = SnmpV3Username;
listViewItem.SubItems[0].Text = SecurityMode.ToString();
listViewItem.SubItems[1].Text = AuthenticationProtocol.ToString();
listViewItem.SubItems[2].Text = PrivacyProtocol.ToString();
listViewItem.Tag = this;
}
call ToListViewItem(selectedItem);
you don't have to assign the modified item back to the collection, because you use a reference, which means you've just modify the same object that's in the ListView
I did a quick test and the method seems to modify texts of existing items without issues.
ListViewItems have a bool Selected property that you can toggle to make them selected or not selected.
A much simpler solution, that worked for me:
lstVwSNMPv3Settings.Items[lstVwSNMPv3Settings.SelectedIndices[0]] = myNewItem;
But be careful to first make sure there is an item selected:
if (lstVwSNMPv3Settings.SelectedIndices.Count > 0) { ... }

Categories