How to update text in a listview without clearing it - c#

I am making a hack for a game that reads all of the enemies information (location, health, etc.) and stores it accordingly in a listview. I am using this method currently:
listView1.Items.Clear();
string[] arr = new string[6];
ListViewItem item;
for (int i = 0; i < engine.maxPlayers; i++)
{
engine.enemy[i].readInfo(i);
arr[0] = i.ToString()
arr[1] = engine.enemy[i].name.ToString();
arr[2] = engine.enemy[i].getRank(csgo.enemy[i].compRank);
arr[3] = engine.enemy[i].Wins.ToString();
arr[4] = engine.enemy[i].health.ToString();
arr[5] = engine.enemy[i].armor.ToString();
item = new ListViewItem(arr);
listView2.Items.Add(item);
}
This is being done every 200 ms to assure real time information.
This works to an extent. It does show everything correctly. However, it has 2 big flaws.
1: it flickers constantly when it clears and rewrites the data.
2: If my listview is only 10 columns long and i need to read data for 20 players, i can't scroll down to see the last 10 players because each time it clears, it resets the scroll bar position.
So is it possible to ONLY update the text of a specific text? Say i only want to update enemy[3]'s health and leave the rest. Can this be done? I don't need to redraw some information like wins and rank because they won't be changing during the game.

Yes, you can use the indexer to directly reference the item you wish to update. For example:
listView2.Items[playerIndex] = new ListViewItem(arr);
As for accomplishing this relative to your game engine, you'll want to use the Reconciliation Design Pattern, which involves the following given game engine list (Master), and list view (Target):
Create a temporary of items or keys in Target. Call this ToDelete.
Step through Master, add any entries that aren't in Target, update those that are. Delete from ToDelete for each match to Target.
Delete from Target all items remaining in ToDelete.
Update: as Slai mentions, you can update via the SubItems member if you don't want to even replace an entire row.

I believe you have to first initially fill the list view with the data from the first call, and then every 500ms-1000ms (I think that 200ms offers nothing), refresh the list view with fresh data.
Something like:
private void InitialPopulation()
{
var listOfItems = new List<ListViewItem>();
for (int i = 0; i < engine.maxPlayers; i++)
{
engine.enemy[i].readInfo(i);
var item = new ListViewItem(i.ToString());
item.Name = engine.enemy[i].name;
item.SubItems.Add(engine.enemy[i].name);
item.SubItems.Add(engine.enemy[i].getRank);
item.SubItems.Add(engine.enemy[i].Wins);
item.SubItems.Add(engine.enemy[i].health);
item.SubItems.Add(engine.enemy[i].armor);
listOfItems.Add(item);
}
listView1.BeginUpdate();
listView1.Items.Clear();
listView1.Items.AddRange(listOfItems.ToArray());
listView1.EndUpdate();
}
private void RefreshData()
{
listView1.BeginUpdate();
var listOfItems = new List<ListViewItem>();
var playersNames = new List<string>();
var itemsNames = new List<string>();
for (int i = 0; i < engine.maxPlayers; i++)
{
engine.enemy[i].readInfo(i);
playersNames.Add(engine.enemy[i].name);
var items = listView1.Items.Find(engine.enemy[i].name, false);
switch (items.Length)
{
case 1: // update
items[0].SubItems[0].Text = engine.enemy[i].name;
items[0].SubItems[1].Text = engine.enemy[i].getRank;
items[0].SubItems[2].Text = engine.enemy[i].Wins;
items[0].SubItems[3].Text = engine.enemy[i].health;
items[0].SubItems[4].Text = engine.enemy[i].armor;
break;
case 0: // add
var item = new ListViewItem(i.ToString());
item.Name = engine.enemy[i].name;
item.SubItems.Add(engine.enemy[i].name);
item.SubItems.Add(engine.enemy[i].getRank);
item.SubItems.Add(engine.enemy[i].Wins);
item.SubItems.Add(engine.enemy[i].health);
item.SubItems.Add(engine.enemy[i].armor);
listOfItems.Add(item);
break;
}
}
if (listOfItems.Count > 0)
listView1.Items.AddRange(listOfItems.ToArray());
foreach (ListViewItem item in listView1.Items)
itemsNames.Add(item.Name);
// check if there are more listview items than data.
if (itemsNames.Count > playersNames.Count)
foreach (var name in itemsNames.Except(playersNames))
listView1.Items.RemoveByKey(name);
listView1.EndUpdate();
}
I assumed that engine.enemy[i].name is unique for each palyer.
Maybe there are some errors in the code since I have not written anything for winforms for a long time, but I think you should get the meaning.

Related

How to populate over multiple columns?

I am working on an 'attendance' system for the school I work at. I'm using a listview for this, but noticed that there's simply not enough space to house all employees in one list. So I want to add a second column (or at least, another 'instance' of the three columns currently used). How do I manage to get such job done?
I have been thinking about workarounds such as a second listview instance, but that would make things only more complex in my opinion. So I'd like to stick by one.
See image below for visual description of what I want to get; I want the output to first fill the left part of the listview, then the right one.
And this is the part of my code that it's (probably) about:
public void LoadEmployees()
{
lvEmployees.View = View.Details;
lvEmployees.GridLines = true;
lvEmployees.Items.Clear();
List<Employees> data = database.LoadEmployees();
for (int i = 0; i < data.Count; i++)
{
// Define the list items
ListViewItem emp = new ListViewItem(data[i].Abbreviation);
emp.SubItems.Add(data[i].Name);
emp.SubItems.Add(data[i].Status);
// Add the list items to the ListView
lvEmployees.Items.Add(emp);
}
}
With lvEmployees1 and lvEmployees2 (or left/right), two ListView.
We can simply split the List<Employees> data in half.
lvEmployees1.View = View.Details;
lvEmployees1.GridLines = true;
lvEmployees1.Items.Clear();
lvEmployees2.View = View.Details;
lvEmployees2.GridLines = true;
lvEmployees2.Items.Clear();
List<Employees> data = database.LoadEmployees();
int half = data.Count / 2;
for (int i = 0; i < data.Count; i++)
{
// Define the list items
ListViewItem emp = new ListViewItem(data[i].Abbreviation);
emp.SubItems.Add(data[i].Name);
emp.SubItems.Add(data[i].Status);
if (i <= half) lvEmployees1.Items.Add(emp);
else lvEmployees2.Items.Add(emp);
}
To create the second "grid", you can simply copy past the element in the designer.

How does an object within an ObservableCollection gets overwrited when adding another object within the Collection

ObservableCollection<TypeA> TempList = ObservableCollection<TypeA>();
List<TypeB> FullValues = listB;
Count = intA;
foreach (var ListA in AllList)
{
for (var i = 1; i < Count; i++)
{
ListA.Name = FullValues[i].Name;
ListA.Surname = FullValues[i].Surname;
ListA.Amount = FullValues[i].Amount;
ListA.Address = FullValues[i].Address;
ListA.Source = FullValues[i].Source;
ListA.OrderCreationDate = FullValues[i].Date;
ListA.Type = FullValues[i].Date;
ListA.Commerce = FullValues[i].Commerce;
TempList.Add(ListA);
}
}
Hello, I'm using an ObservableCollection of a specific type to be filled by a local variable of the same type but using the Add method adds the new object and overwrites the preceding.
I need to add values to the same object but more than one time. So i am using an ObservableCollection to store the values more than time
Tried with List and ObservableCollection
Expected Result: The list for example contains 3 values with
ListA[0], ListA[1], ListA[2]
Actual Result:
ListA[2], [2], [2]
Thank you for your help
Edited: I tried to add another case of the issue, this may look dumb but i need to duplicate the main object to add other values to them and keep them within the same list and as someone said, i am still using the same object thats why it is not changing:
List<string> RoadType = List<string>();
RoadType = somevalues;
foreach(var roads in listofroads)
{
roads.street = "xxxxx"
roads.country= "xxxxx"
//I want to duplicate roads twice
TestList.Add(roads);
TestList.Add(roads);
count = 0;
//Changing the properties of the object
foreach(var newroads in TestList)
{
roads.street = RoadType[i].street;
roads.country = RoadType[i].country;
count++
MainRoadList.Add(newroads);
}
MainRoadList.Add(roads);
}

How to Filter/Search List in Unity

I am trying to think of a way to filter a list of buttons that I dynamically create, based on values looped through from the SQlite DB. Each button is tagged with a student name and there could be a large number of buttons, hence the need to filter the buttons.
I create my buttons as so:
public RectTransform GridWithNameElements;
public GameObject StudentNamePrefabButton;
while (reader.Read())
{
//create a new button object and use the prefab button to make sure spacing etc is correct
goButton = (GameObject)Instantiate(StudentNamePrefabButton);
//set the parent of the button
goButton.transform.SetParent(GridWithNameElements, false);
goButton.transform.localScale = new Vector3(1, 1, 1);
//set the text of the button. Array value is 0 as the student name is always at position 0 on each iteration
goButton.GetComponentsInChildren<Text>()[0].text = reader["fullName"].ToString()+" | "+ reader["studentNumber"].ToString();
goButton.name = reader["studentID"].ToString();
Button tempButton = goButton.GetComponent<Button>();
int tempInt = i;
tempButton.onClick.AddListener(() => ButtonClicked(tempInt));
i++;
Debug.Log(goButton.name);
}
Then I created an input field and attached a script to the onValueChanged in the input field and attempted to write a script.
//This method is called when the student attempts to search for their own name when taking the individual quiz
public void SearchExistingStudentName()
{
//Get the name entered from the text field
string student_name = searchExistingStudentNameInput.text;
//Add names that exist to the new list
List<string> names = new List<string>();
names.Clear();
//sets an id to the onclick listener of newly created buttons
int x = 0;
//if a new button object exists and the name was entered and then removed, clear it from the list and remove the created button
if (student_name == "")
{
//Clear the filtered names List
names.Clear();
x = 0;
//Destroy the create filter buttons if the user clears the text area
GameObject[] objects = GameObject.FindGameObjectsWithTag("filterButton");
//loop through the buttons that share the tag "filterButton"
for (int count = 0; count < objects.Length; count++)
{
Destroy(objects[count]);
Debug.Log("Number of objects to be deleted " + objects.Length);
}
//Loop through and show all children
foreach (Transform child in GridWithNameElements)
{
child.gameObject.SetActive(true);
}
}
else if (student_name != "")
{
int count = 0;
int filteredNameCount = 0;
int filteredIDCount = 1;
//loop through the list of buttons with student names
for (int i = 0; i < GridWithNameElements.childCount; i++)
{
Debug.Log("Children of grid "+GridWithNameElements.childCount);
//Check if the user has typed their name with capitals or lowercase etc, and check the inputted value against names already in the list
//If true, proceed
if (GridWithNameElements
.GetComponentsInChildren<Text>()[i].text.Contains(student_name) || GridWithNameElements
.GetComponentsInChildren<Text>()[i].text.ToLower().Contains(student_name) || GridWithNameElements
.GetComponentsInChildren<Text>()[i].text.ToUpper().Contains(student_name))
{
//If the name entered contains letters found in the parent GridWithNameElements, then add them to the list array and their name value (unique id)
names.Add(GridWithNameElements.GetComponentsInChildren<Text>()[i].text.Replace(#"[", string.Empty).Replace(#"]", string.Empty));
names.Add(GridWithNameElements.GetChild(i).name); //this is the unique id of the student
Debug.Log("Number of items in filtered names list " + names.Count);
//Loop through and hide all children and hide them
if (count == 0)
{
foreach (Transform child in GridWithNameElements)
{
child.gameObject.SetActive(false);
}
}
count++;
//Then create a button that represents a name added to the names List
newButton = (GameObject)Instantiate(StudentNamePrefabButton);
//set the parent of the button
newButton.transform.SetParent(GridWithNameElements, false);
newButton.transform.localScale = new Vector3(1, 1, 1);
//set the text of the button. Array value is 0 as the student name is always at position 0 on each iteration
newButton.GetComponentsInChildren<Text>()[filteredNameCount].text = names[filteredNameCount].ToString();
newButton.name = names[filteredIDCount].ToString().Trim();
newButton.tag = "filterButton";
filteredNameCount++;
filteredIDCount++;
//Then add a click listener to the button
Button tempButton = newButton.GetComponent<Button>();
int tempInt = x;
tempButton.onClick.AddListener(() => ButtonClicked(tempInt));
x++;
// Debug.Log("Student Unique ID " + newButton.name);
}
}
count = 0;
}
}
I was hoping to simply Loop through all list items, and hide those that don't match the search query. However, at the moment, my loop is wrong, as I get an out of bounds exception (which I think is to do with adding a new child element dynamically when making a search). I see this in the console log, however, the program executes as expected (so this is a problem, but currently not the biggest).
I am hiding the original list and showing the new list based on matching criteria.
However, I am only ever returning one possible name, rather than a bunch of possible choices. For example, if I had the name Ben Jones and Bob Dylan, based on my code at the moment, I am only ever able to return Ben Jones and never Bob Dylan.
I feel like I am going the wrong way around doing this because I want something similar to this, and unable to recreate it. I am trying to figure out, however, if I am heading in the right direction or not.
UPDATE
I think I have found the reason for the IndexOutOfRangeException: Array index is out of range. It is because whenever I type a letter, it is calling the SearchExistingStudentName() method. This means that as long as the letter typed is found within one of the names, it is added to list - only the letter. This is why I can only return one name, rather than a list of possible names. Therefore I think the if statement needs amending, which I am trying to look into now.
I have managed to narrow down the array exception in the code to this section:
//Loop through and hide all children and hide them
if (count == 0)
{
foreach (Transform child in GridWithNameElements)
{
child.gameObject.SetActive(false);
}
}
count++;
//Then create a button that represents a name added to the names List
newButton = (GameObject)Instantiate(StudentNamePrefabButton);
//set the parent of the button
newButton.transform.SetParent(GridWithNameElements, false);
newButton.transform.localScale = new Vector3(1, 1, 1);
//set the text of the button. Array value is 0 as the student name is always at position 0 on each iteration
newButton.GetComponentsInChildren<Text>()[filteredNameCount].text = names[filteredNameCount].ToString();
newButton.name = names[filteredIDCount].ToString().Trim();
newButton.tag = "filterButton";
filteredNameCount++;
filteredIDCount++;
Since you have tagged this with Unity3D I'm assuming you are looking to use this code in a game. If that is the case I would not recommend using LINQ, if the same result can easily be obtained in a more traditional way. That being said, I'll show both ways to do it.
List<string> names = GridWithNameElements.Where(nameInList => nameInList.Contains(input))
The above finds all the names where nameInList.Contains(input) evalutes to true.
In a loop you'd do the following.
List<string> names = new List<string>();
foreach (string nameInList in GridWithNameElements)
{
if (nameInList.Contains(input)
{
names.Add(nameInList)
}
}
I am not entirely confident on the types of the variables but I think the structure should be clear enough. Feel free to ask some more if it isn't.
Managed to solve this myself in the end, and although it is far from an elegant solution, it works. I am now going to work on improving the code itself, but for those that might want a starting point, I thought I would share what I did.
As mentioned in my question, I loop out a bunch of buttons onto my main, unfiltered scrollable list as so.
while (reader.Read())
{
//create a new button object and use the prefab button to make sure spacing etc is correct
GameObject goButton = (GameObject)Instantiate(StudentNamePrefabButton);
//set the parent of the button
goButton.transform.SetParent(GridWithNameElements, false);
goButton.transform.localScale = new Vector3(1, 1, 1);
//set the text of the button. Array value is 0 as the student name is always at position 0 on each iteration
goButton.GetComponentsInChildren<Text>()[0].text = reader["fullName"].ToString() + " | " + reader["studentNumber"].ToString();
goButton.name = reader["studentID"].ToString();
Button tempButton = goButton.GetComponent<Button>();
int tempInt = i;
tempButton.onClick.AddListener(() => ButtonClicked(tempInt));
i++;
Debug.Log(goButton.name);
}
I then created a user inputfield in my UI, so the user could search for their name. Then attached a listener. To do this go to the Unity editor and attach your script to the On Value Changed method.
Every-time a user then types into your inputfield, your attached script is called.
I then ended up doing the following - again, I stress, I have just got this working so not a perfect example, but at least a starting point and there might still be more bugs to sort out. I hope it helps someone as I didn't find any complete examples when I was looking.
//This method is called when the student attempts to search for their own name when taking the individual quiz
public void SearchExistingStudentName()
{
//Get the name entered from the text field
string student_name = searchExistingStudentNameInput.text;
//Create a list to store all the user names
List<string> filteredNamesList = new List<string>();
//Add another list to store the user unique ids
List<string> filteredNameIDList = new List<string>();
//Clear both lists before using them
filteredNamesList.Clear();
filteredNameIDList.Clear();
//sets an id to the onclick listener of newly created buttons
int x = 0;
//if a new button object exists and the name was entered and then removed, clear it from the list and remove the created button
if (student_name == "")
{
//Clear the filtered filteredNamesList List
filteredNamesList.Clear();
filteredNameIDList.Clear();
x = 0;
//Destroy the create filter buttons if the user clears the text area
GameObject[] objects = GameObject.FindGameObjectsWithTag("filterButton");
//loop through the buttons that share the tag "filterButton"
for (int count = 0; count < objects.Length; count++)
{
Destroy(objects[count]);
Debug.Log("Number of objects to be deleted " + objects.Length);
}
//Loop through and show all children
foreach (Transform child in GridWithNameElements)
{
child.gameObject.SetActive(true);
}
GridWithNameElements.gameObject.SetActive(true);
GridWithNameElements2.gameObject.SetActive(false);
}
else if (student_name != "")
{
//loop through the list of buttons in the main/unfiltered list with student filteredNamesList
for (int i = 0; i < GridWithNameElements.childCount; i++)
{
//Check if the user has typed their name with capitals or lowercase etc, and check the inputted value against filteredNamesList already in the list
//If true, proceed
//Get the name value that contains entered character
if (GridWithNameElements
.GetComponentsInChildren<Text>()[i].text.Contains(student_name) || GridWithNameElements
.GetComponentsInChildren<Text>()[i].text.ToLower().Contains(student_name) || GridWithNameElements
.GetComponentsInChildren<Text>()[i].text.ToUpper().Contains(student_name))
{
//Do not allow duplicates to the list
if (filteredNamesList.Distinct().Count() == filteredNamesList.Count())
{
//If the name entered contains letters found in the parent GridWithNameElements, then add them to the list array and their name value (unique id)
filteredNamesList.Add(GridWithNameElements.GetComponentsInChildren<Text>()[i].text.Replace(#"[", string.Empty).Replace(#"]", string.Empty));
filteredNameIDList.Add(GridWithNameElements.GetChild(i).name); //this is the unique id of the student
}
//end of if statement
}
//end of main loop
}
//hide original list
GridWithNameElements.gameObject.SetActive(false);
//show filtered list
GridWithNameElements2.gameObject.SetActive(true);
//Destroy the created filter buttons if the user presses another button, as previously
//added filteredNamesList might need to be ruled out.
GameObject[] objects = GameObject.FindGameObjectsWithTag("filterButton");
//loop through the buttons that share the tag "filterButton"
for (int count = 0; count < objects.Length; count++)
{
Destroy(objects[count]);
Debug.Log("Number of objects to be deleted " + objects.Length);
}
int filteredNameIDCount = 0;
foreach (string nameInList in filteredNamesList)
{
//Then create a button that represents a name added to the filteredNamesList List
newButton = Instantiate(StudentNamePrefabButton);
//set the parent of the button
newButton.transform.SetParent(GridWithNameElements2, false);
newButton.transform.localScale = new Vector3(1, 1, 1);
//set the text of the button. Array value is 0 as the student name is always at position 0 on each iteration
newButton.GetComponentsInChildren<Text>()[0].text = nameInList.ToString();
newButton.name = filteredNameIDList[filteredNameIDCount].ToString().Trim();
newButton.tag = "filterButton";
Debug.Log("Filtered Name " + nameInList.ToString() + " Their ID " + filteredNameIDList[filteredNameIDCount].ToString().Trim());
filteredNameIDCount++;
}
//end of loop
//Then add a click listener to the button
Button tempButton = newButton.GetComponent<Button>();
int tempInt = x;
tempButton.onClick.AddListener(() => ButtonClicked(tempInt));
x++;
// Debug.Log("Student Unique ID " + newButton.name);
}
}

Clearing Multi-Selected Items From Listbox C#

What i've tried:
try 1:
for(int x = listBox1.SelectedIndices.Count - 1; x>= 0; x--)
{
int idx = listBox1.SelectedIndices[x];
listBox2.Items.Add(listBox1.Items[idx]);
listBox1.Items.RemoveAt(idx);
}
try 2:
ArrayList tmpArr = new ArrayList();
foreach (object obj in listBox1.SelectedItems)
{
listBox2.Items.Add(obj);
tmpArr.Add(obj);
}
foreach (object obj in tmpArr.ToArray())
{
listBox1.Items.Remove(obj);
}
Also tried everything in the following post: How to remove multiple selected items in ListBox?
Still nothing worked. What am I doing wrong?
var selectedItems = new object[listBox1.SelectedItems.Count];
listBox1.SelectedItems.CopyTo(selectedItems, 0);
foreach (var item in selectedItems)
{
listBox1.Items.Remove(item);
}
or with a bit of LINQ to simplify the code:
foreach (var item in listBox1.SelectedItems.Cast<object>().ToArray())
{
listBox1.Items.Remove(item);
}
The reasoning here is that you get all the selected items and put them into another list first. The original issue is that any change you make to the ListBox will change things like SelectedItems and SelectedIndices. Once you've created an independent array and put the selected items into it, nothing you do to the ListBox will affect that array so you can just enumerate it normally.
listbox1.BeginUpdate();
for (int x = listBox1.SelectedIndices.Count - 1; x >= 0; x--)
{
int idx = listBox1.SelectedIndices[x];
listBox1.Items.RemoveAt(idx);
}
listbox1.EndUpdate();
If you cannot guarantee that every object in the list is unique, then this is the correct way to do it, to ensure that the correct selected items get removed.
If you have multiples of the same object in your listbox, you have to refer to them by "index", otherwise if you remove them by "item" it will remove the first instance of any matching items it finds.
I am in the process of writing a bus route planner which called for replication of the waypoint markers in the list. These were stored as strings, so for example I might have had "w1", "w2", "w3"... "w2" (think of a bus going down a high street, looping round a couple of blocks and then returning down the other side to understand why I have that... I only need waypoint markers in the centre of the road, not in each lane)
If I had selected the last "w2" marker as part of a range and used the selectedItem() method to to remove them, it would have removed the first "w2", not the second one. By using the SelectedIndex() method, it removes based on position, not value, so duplicate values are left safely intact.
I just wanted to add that as I have just been dealing with this very same problem, so saw first hand the problem removing by SelectedItem() caused.

C# - in ListView's large icon view I cannot update icons properly

I am using ListView's large icon view, to view database inserts with image per insert.
When a new insert is made, I clear the whole ListView and add all inserts using the following code. It works perfectly, only when run for the first time.
The second time (after a new insert is added), I get all the items, but with missing images (some of them are missing and some of them are scrambled)
private void updateListView()
{
myListView.Clear();
myListView.Items.Clear();
myConnection.connect();
List<String> myValues = myConnection.getMyValues();
List<String> myImages = myConnection.getMyImages();
ImageList myImageList = new ImageList();
myImageList.ImageSize = new Size(256, 256);
myImageList.ColorDepth = ColorDepth.Depth32Bit;
for (int i = 0; i < myValues.Count; i++)
{
myListView.Items.Add(myValues[i]);
myListView.Items[i].ImageIndex = i;
myImageList.Images.Add(Image.FromFile(myImages[i]));
}
myListView.LargeImageList = myImageList;
myConnection.close();
myListView.Refresh();
}
I've checked with debug and the values/filepaths are correct.
Try associating the myListView first before you add the new item:
myListView.LargeImageList = myImageList; ,
then add item...
myImageList.Images.Add(Image.FromFile(myImages[i]));
also what is churchListView..??? where are you declaring it.. if so replace the code example to fit the churchListView or the appropriate ListView variable...
I found it:
The problem was that I had the Sort property of the ListView on.
This means that the ListView sorted it's Items automatically.
So in these lines:
myListView.Items.Add(myValues[i]);
myListView.Items[i].ImageIndex = i;
An item is added and after that the last item (i) is attached to the last image (i)
In between these lines the newly added item is auto-sorted alphabetically, so it isn't
indexed at i anymore, but something else. So the image is assigned to a random item.
Instead I did this:
ListViewItem lastAddedListViewItem = myListView.Items.Add(myValues[i]);
lastAddedListViewItem.ImageIndex = i;
And it worked

Categories