I have a text file that is read on the program startup and it populates a simple array. The array is then used to populate a listBox. Now what I want to do is to let the user select a item in the listBox and delete it. When the item is deleted the array should update accordingly and the listBox should be populated with the new array again(I have a function for that).
Here's what I use to populate Array:
string[] cArray = new string[10];
int counter, count;
public void loadArray()
{
try
{
StreamReader inputfile = File.OpenText("Classes.txt");
classList.Items.Clear();
while (!inputfile.EndOfStream)
{
cArray[count] = inputfile.ReadLine();
count++;
}
inputfile.Close();
}
catch (Exception e)
{
MessageBox.Show(e.Message, "Loading Error!");
}
}
And this is custom function I made to display the data into the listBox whenever I make any updates.
public void displayClasses()
{
try
{
classList.Items.Clear();
for (counter = 0; counter < count; counter++)
{
classList.Items.Add(cArray[counter]);
}
}
catch (Exception e)
{
MessageBox.Show(e.Message, "Displaying Error!");
}
}
I tried this using Nirupam's reply:
private void deleteButton_Click(object sender, EventArgs e)
{
try
{
while (classList.SelectedItems.Count != 0)
{
classList.Items.RemoveAt(classList.SelectedIndex);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
DeleteClasses(classList.SelectedIndex);
}
public void DeleteClasses(int indexId)
{
cArray = cArray.Where((source, index) => index != indexId).ToArray();
displayClasses();
}
The class does get deleted but with the displayClasses() it comes back, meaning it isn't being deleted from the array.
In comments, I guessed that you were trying to ask us two separate questions:
How can I allow a user to delete an item from a ListBox control, and capture the deleted item's index?
I'm actually not going to answer this one just yet, because we're still working out how the deletion will be performed.
How can I remove an item from an array, given that index?
The best solution to this problem is to, instead of an array, use a List<T>. In your case, a List<string>.
I don't know whether you're familiar with lists, but if not, a list is like a "mutable array." You can add or remove items relatively freely. This will accomplish what you asked for in your comment, so that removing the item at index 2 would shift all subsequent items down one.
To remove an item, use the RemoveAt method:
List<string> strs = new List<string>();
strs.Add("AAA");
strs.Add("BBB");
strs.Add("CCC");
strs.RemoveAt(1); // remove "BBB"
Console.WriteLine(strs[0]); // yields AAA
Console.WriteLine(strs[1]); // yields CCC
If you're stuck with an array for other reasons, you'd have to write the "shifting" logic yourself. I can help you with that if it's the case, but I won't bother otherwise.
For what it's worth, a list will also clean up your reading method.
This is probably how I would perform the remove operation in the array.
string[] args = new string[3];
args[0] = "AAA";
args[1] = "BBB";
args[2] = "CCC";
int indexToRemove = 1;
for (int v = indexToRemove + 1; v < args.Length; v++)
{
if(v > 0)
{
args[v - 1] = args[v];
}
}
args[args.Length - 1] = null;
write a another function DeleteClasses(int indexId) for better modularity of code which will return Array Data.It will be called from inside your displayClasses() methode .Inside it write down code according to Algo given below:
Step 1 :
Remove the Item in cArray using parameter IndexId which we will send when we call DeleteClasses(int indexId) method.
Code will be something like this:
cArray = cArray .Where((source, index) => index != indexId).ToArray();
return that cArray from where it will be called.
Step 2:
After That you will have updated Array when user choose to delete an item.
than Load your ListBox by calling displayClasses();
Note: Inside displayClasses() you should have code like this below:
try{
classList.Items.Clear();
classList.Items = cArray.ToList(); //here cArray is updated data array
}
catch (Exception e)
{
MessageBox.Show(e.Message, "Displaying Error!");
}
Overall :
Overall code looks something like this below:
Public string[] DeleteClasses(int indexId)
{
//indexId will be depends upon user selection on demand for delete
// operation.
cArray = cArray .Where((source, index) => index != indexId).ToArray();
}
public void displayClasses()
{
try{
int indexId=classList.SelectedItemIndex;//
string[] cArray = DeleteClasses(int indexId);
classList.Items.Clear();
classList.Items = cArray.ToList(); //here cArray is updated data array
}
catch (Exception e)
{
MessageBox.Show(e.Message, "Displaying Error!");
}
}
Related
I have an auto generated Datagrid that shows queried data from an API. The results come in array form and i put them in a DataTable, that are shown in Datatgrid. The array length are not always the same and the columns are not "even".
|--DTColumn1---DTColumn2-----|
|---Data1----|---Data1-------|
|---Data2----|---Data3-------|
|---Data3----|---Data5-------|
|---Data4----|---emptycell---|
|---Data5----|---emptycell---|
The "DTColumn" are the arrays that contain the data, that are similar, but parts could be missing and that is not an error. I don't know the data that I will get in runtime, but i know that it will contain some similar data.
Any advice to make it look like this:
|--DTColumn1---DTColumn2-----|
|---Data1----|---Data1-------|
|---Data2----|---emptycell---|
|---Data3----|---Data3-------|
|---Data4----|---emptycell---|
|---Data5----|---Data5-------|
EDIT:
Code structure:
private void buttonget_Click(object sender, RoutedEventArgs e)
{
sngrid.ItemsSource = null;
string dtnumber = DTBox.Text;
if (dtnumber == "")
{
MessageBox.Show("NO DATA!");
}
else
{
string[] dtresvalues;// this is were the DTColumn's come from and the number a of loop's for the second apicall
int s = apicall.Check..; //<- check if input can be handled and get dtresvalues
if (s != 5)
{
//API error handling here
}
else
{
var list = dtresvalues.Where((value, index) => index % 2 == 0).ToArray();// get rid of excess/usles data
string[] predt = list.ToArray();// prepared data, actual DTColumn's in array
DataTable table = new DataTable();
for (int i = 0; i < predt.Length; i++)//loop untill DTColumn run out
{
string kerdt = predt[i];// set current DTColumn for API
int si = apicall.Get...;// if no error, it returns the resoultvalues array (data1, data2,...)
if (si != 0)
{
//API error handling here
}
else
{
table.Columns.Add(kerdt);//set column name in table
if (i == 0)//this loop down, add's resoultvalues of API to current column of table(data1,data2, ..)
{
foreach (string sd in resoultvalues)
{
table.Rows.Add(sd);
}
}
else
{
while (table.Rows.Count < resoultvalues.Length)
{
table.Rows.Add();
}
for (int j = 0; j < resoultvalues.Length; j++)
{
table.Rows[j].SetField(i, resoultvalues[j]);
}
}
}
}
// add finished table to datagrid
sngrid.ItemsSource = table.DefaultView;
}
Also resoultvalues is an array.
I don't know what type of data is inside DTColumn1 and DTColumn2, but I imagine that there is something to let you know that, for example, DTColumn1[0] is relative to DTColumn2[3].
You can create two new arrays and use it as containers for your arrays of data, while filling the new array mind the empty fields.
Example:
string[] DTColumn1 = {
"1-foo",
"2-bar",
"3-foobar"
};
string[] DTColumn2 = {
"1-foo2",
"3-foobar2"
};
//Find the longest array of the two
string[] longestArray = DTColumn1;
string[] shortestArray = DTColumn2;
if (DTColumn2.Length > longestArray.Length) {
longestArray = DTColumn2;
shortestArray = DTColumn1;
}
//Instantiating new lists to show data
List<string> col1 = new List<string>();
List<string> col2 = new List<string>();
//Filling "interface" lists with data
foreach (void value_loopVariable in longestArray) {
value = value_loopVariable;
col1.Add(value);
}
//This can be tricky, but I really have no idea of how your data is structured
foreach (void value1_loopVariable in col1) {
value1 = value1_loopVariable;
foreach (void value2_loopVariable in shortestArray) {
value2 = value2_loopVariable;
if (value1[0].Equals(value2[0])) {
col2.Add(value2);
break;
}
//When the program reaches this point means that there is no corrispondace of data, so we add an empty value to col2
col2.Add("");
}
}
//Here you'll have col1 and col2 filled with data
This will result in this:
|--DTColumn1---|--DTColumn2-----|
|---1-foo------|---1-foo2-------|
|---2-bar------|---emptycell----|
|---3-foobar---|---3-foobar2----|
You may notice that if DTColumn2 is the longer array it will be placed in the first column of the table.
I wrote down a raw example just to give you an idea, please note that this is untested code
I'm working on a project and I'm a beginner in programming in c#, and somehow there is a problem that I cannot solve. How it happened: In executing the code, the application launches successfully but an exception
shows that the "Index was outside the bounds of array". Afterwards, I was able to click items on a listbox and it shows the second object on the textbox. So... It seems like it works(clicking on listbox's item) but I cannot figure out why it would throw an exception about the array. Beneath is the current code that I have.
**Update:I sincerely apologize. I uploaded the wrong code. It is suppose to be this code:
Code:
struct studentInfo
{
public string studentID;
public string major;
}
public partial class Form1 : Form
{
private List<studentInfo> studentList = new List<studentInfo>();
public Form1()
{
InitializeComponent();
}
private void ReadInputFile()
{
try
{
StreamReader inputFile;
string line = "";
studentInfo student = new studentInfo();
char[] delimiter = { ',' };
inputFile = File.OpenText("Student Info.txt");
while (!inputFile.EndOfStream)
{
line = inputFile.ReadLine();
string[] token = line.Split(delimiter);
student.studentID = token[0];
student.major = token[1];
studentList.Add(student);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void DisplaystudentID()
{
foreach (studentInfo student in studentList)
{
studentInfoListBox.Items.Add(student.studentID);
}
}
private void DisplayNames()
{
}
private void button1_Click(object sender, EventArgs e)
{
ReadInputFile();
DisplaystudentID();
}
private void studentInfoListBox_SelectedIndexChanged(object sender, EventArgs e)
{
int index = studentInfoListBox.SelectedIndex;
majorTextBox.Text = studentList[index].major;
}
private void button3_Click(object sender, EventArgs e)
{
Application.Exit();
}
}
My guess would be that SelectedIndexChanged is ran at the start (before you select anything) and at that point nameListBox.SelectedIndex would be -1 and you can't get the "negative 1 position's item" in a list. I would make sure that the selected index is valid
https://msdn.microsoft.com/fr-fr/library/system.windows.forms.listbox.selectedindex(v=vs.110).aspx
"A zero-based index of the currently selected item.A value of negative one (-1) is returned if no item is selected."
I would change the code as such:
private void nameListBox_SelectedIndexChanged(object sender, EventArgs e)
{
int index = nameListBox.SelectedIndex;
if(index !=-1)
{
phoneLabel.Text = phoneList[index].phone;
}
// else do nothing, the selected item didn't really change, it's just called for the first time, think of it as the control saying "hey i just got created and i'm notifying you that the selected item is now nothing"
}
You have to guard SelectedIndex, when controls are initially created SelectedIndex is set to -1
private void nameListBox_SelectedIndexChanged(object sender, EventArgs e)
{
if(nameListBox.SelectedIndex >=0)
{
int index = nameListBox.SelectedIndex;
phoneLabel.Text = phoneList[index].phone;
}
}
The only array handling you have in your program is this:
entry.name = tokens[0];
entry.phone = tokens[1];
Therefore the reason is that one of the lines in your text file does not have a comma, so tokens does not have 2 parts.
A common reason for this is simply having a file that has a linefeed after the final real entry, thereby having an empty line as the last line.
I would simply handle this here:
if (tokens.Length < 2)
continue;
You didn't let us know where exactly the exception is occurred but as I see it might be in this part
line = inputFile.ReadLine();
string[] tokens = line.Split(delim);
entry.name = tokens[0];
entry.phone = tokens[1];
If your line is empty or doesn't have "," you will get exception in the next line
Also you need to check the access to list in the index location in nameListBox_SelectedIndexChanged.
"Index was outside the bounds of array" exception occurs when you try to access an element which is not exits in the list. I believe you are getting this exception when you are click on the last item on your list.
In your nameListBox_SelectedIndexChanged method you one of following.
int index = nameListBox.SelectedIndex -1;
or
phoneLabel.Text = phoneList[index-1].phone;
I have a listview and I want when a button is pressed to delete the selected item. Additionally, I use the item for some other actions. Basically, I use some letters of the item' s string to match with a file and delete it. This works if the selected item is the first one in the listview, but doesn' t work if it's second, third etc.
private void delete_button_Click(object sender, EventArgs e)
{
for (int i = 0; i < listView1.Items.Count; i++)
{
if (listView1.Items[i].Selected)
{
string var1 = listView1.SelectedItems[i].ToString(); //error
string var2 = var1.Substring(31, 5);
... // code for other actions
listView1.Items[i].Remove();
i--;
}
}
}
It thorws error
ArgumentOutofRangeException was not handled" - Invalid argument value
of '1' is not valid for 'index'
I don't understand what's the problem and why it works only if it' s the first item.
Selected items contains only those selected, and yet you are iterating over the entire collection.
for (int i = 0; i < listView1.Items.Count; i++)
{
if (listView1.Items[i].Selected)
{
string var1 = listView1.Items[i].ToString(); // <-------
string var2 = var1.Substring(31, 5);
... // code for other actions
listView1.Items[i].Remove();
i--;
}
}
I think the problem is, that your index increases while listView1.Items is getting smaller.
You check for Items to start with but then check the index against SelectedItems
If there are 4 elements in Items and only the 4th is selected then SelectedItems has 1 item but i would be 4
for (int i = 0; i < listView1.SelectedItems.Count; i++)
{
string var1 = listView1.SelectedItems[i].ToString();
string var2 = var1.Substring(31, 5);
... // code for other actions
listView1.Items[i].Remove();
i--;
}
}
var1 needs to be from Items, not from SelectedItems. Like this:
private void delete_button_Click(object sender, EventArgs e)
{
for (int i = 0; i < listView1.Items.Count; i++)
{
if (listView1.Items[i].Selected)
{
string var1 = listView1.Items[i].ToString(); //NOTE THE DIFFERENCE
string var2 = var1.Substring(31, 5);
... // code for other actions
listView1.Items[i].Remove();
i--;
}
}
In fact though, a better way to do this would be like this:
private void delete_button_Click(object sender, EventArgs e)
{
foreach (var x in listView1.SelectedItems.Select(x => x))
listView1.Items.Remove(x);
}
This is because you are iterating through the items in the list box, and not the selected items. For example if you have 10 items in the box, and you have selected 2, when it gets to the thrird iteration it will fail.
Use this one instead you code:
foreach(var item in listView.SelectedItems){ //todo }
if you have a collection with only the selected ones why are you iterating all the elements?
just do this.
foreach(var var1 in listView1.SelectedItems.ToArray())
//the to array is to create e new collection from the list else you get one error when you change it.
{
string var2 = var1.Substring(31, 5);
... // code for other actions
listView1.Items.Remove(var1);
}
Try this;
for (int i = listView1.Items.Count-1; i >=0 ; i--)
{
if (listView1.Items[i].Selected)
{
string var1 = listView1.SelectedItems[i].ToString(); //error
string var2 = var1.Substring(31, 5);
... // code for other actions
listView1.Items[i].Remove();
}
}
I make a listbox and it works perfectly, I also made a search function, but I want to hide the items in the listbox and show them only when searched through index.
here is my code for adding items
private void Savebtn_Click(object sender, EventArgs e)
{
addTolist(gatherItem());
refreshView();
}
private void addTolist(Person p)
{
Person.listperson.Add(p);
}
private void refreshView()
{
listBox1.Items.Add(getItem());
}
private String getItem()
{
String result = null;
foreach (Person p in Person.listperson)
{
result = p.lastname;
}
return result;
}
and this is my code for search
private void button1_Click(object sender, EventArgs e)
{
int index = listBox1.FindString(textBox6.Text);
if (0 <= index)
{
listBox1.SelectedIndex = index;
}
}
Is there a way for this? thanks :)
There is no easy way to do this with winforms. It a lot easier with WPF. With winforms you will have to remove the items. You could technically use databinding, but its not recommended at all, because listbox is supposed to used directly from your code according the MSDN documentation.
On second thought, you could overdraw the listbox and set the item you want to hide to transparent or something, buts its not easy. You may want to consider storing all your values in an array and then loop through array and only adding the elements you want. Or switch to WPF.
Hope this helps.
You can cover a dummy ListBox on your origin one, and set origin one invisible.
Then you can archive the same effect by writing codes like
class DummyItem {
public string text;
public int index;
public override string ToString() {
return text;
}
}
public void build_dummy() {
listbox_dummy.Items.Clear();
for (int i = 0; i < listbox_origin.Items.Count; i++) {
// replace with your own judgement
if (! should_hide(listbox_origin.Items[i])) {
DummyItem item = new DummyItem();
item.text = listbox_origin.Items[i].ToString();
item.index = i;
listbox_dummy.Items.Add(item);
}
}
}
private void listbox_dummy_SelectedIndexChanged(object sender, EventArgs e) {
var item = (DummyItem) listbox_dummy.SelectedItem;
var index = item.index;
listbox_origin.SelectedIndex = index;
}
The easiest way to do this, is to create a List<string> item that will store removed items (or items that do not match the search-phrase given in textbox1), an int variable that will store how many characters there were the last time the TextChanged event was triggered, and then create a function for your listbox element that activates when the phrase in a textbox is updated:
private void textbox1_TextChanged(object sender, EventArgs e) {}
in this function, there are two things that could happen:
if a character was added to textbox1:
you iterate over the elements in your listbox.
remove the ones that do not contain the phrase in textbox1
if a character was removed from textbox1:
look through the List<string> variable you made to store the values you removed from your listbox
add back each item that fits the text in textbox1 (check for duplicates though)
Here's the code I wrote to use as reference:
private List<string> temporarilyRemovedSongs = new List<string>();
private int previousTextLength = 0;
private void filter_songs_textbox_TextChanged(object sender, EventArgs e)
{
List<string> toRemove = new List<string>();
string text = Tools.Strip(this.filter_songs_textbox.Text.ToLower());
if (text.Length > this.previousTextLength)
{
// a char was added, remove songs that do not start with the search-phrase
foreach (string song in this.songs_list.Items)
{
if (!song.ToLower().Contains(text))
{
this.temporarilyRemovedSongs.Add(song);
toRemove.Add(song);
}
}
foreach (string song in toRemove)
{
this.songs_list.Items.Remove(song);
}
}
else
{
// a char was removed; look through removed songs to add back
foreach (string song in this.temporarilyRemovedSongs)
{
if (song.Contains(text) && !this.songs_list.Items.Contains(song))
this.songs_list.Items.Add(song);
}
}
previousTextLength = text.Length;
}
I am having trouble populating a datagridview with items from a string array. Here is the code I used to call the function:
ThreadPool.QueueUserWorkItem((o) =>
ReBuildObjectExplorer();
And the function itself:
try
{
List<ExplorerItem> list = new List<ExplorerItem>();
var item = new ExplorerItem();
for (int i = 0; i < lbl.Length; i++) // lbl = string array with items
{
item.title = lbl[i].Name;
list.Add(item);
}
BeginInvoke((MethodInvoker)delegate
{
explorerList = list;
dgvObjectExplorer.RowCount = explorerList.Count;
dgvObjectExplorer.Invalidate();
});
}
catch (Exception e) { MessageBox.Show(e.ToString(); }
The problem is: Suppose there are 76 items in the array. When I use this code, it ALWAYS adds the 75th item 76 times and nothing else. Why does this happen? I can't seem to figure out what is wrong with my code.
I think you want:
try
{
List<ExplorerItem> list = new List<ExplorerItem>();
for (int i = 0; i < lbl.Length; i++) // lbl = string array with items
{
var item = new ExplorerItem();
item.title = lbl[i].Name;
list.Add(item);
}
BeginInvoke((MethodInvoker)delegate
{
explorerList = list;
dgvObjectExplorer.RowCount = explorerList.Count;
dgvObjectExplorer.Invalidate();
});
}
catch (Exception e) { MessageBox.Show(e.ToString(); }
That is, move the creation of the new ExplorerItem inside the loop rather than outside it. That way a new item is created at each iteration of the loop. If you don't create a new item in each iteration, then you are adding the same item over and over again, changing its title in every iteration.