Convert a PictureBox inside an listBox back into a PictureBox - c#

i have some PictureBoxes in my program with different colors and I want to count, how much boxes there are for one color. So I created a function to count it:
private void cmdCount(object sender, EventArgs e)
{
int counterWhite, counterRed, counterGreen, counterYellow, counterBlue, counterOrange = 0;
if (alreadyAdded == false)
{
lstBox.Items.Add(picA1);
lstBox.Items.Add(picA2);
lstBox.Items.Add(picA3);
//...
alreadyAdded = true;
}
//Log
String value = Convert.ToString(lstBox.Items.Count);
lblLog.Text = "Objects in array: " + value;
for(int i = 0; i < lstBox.Items.Count; i++)
{
if(lstBox.Items[i].BackColor == Color.White)
{
counterWhite += 1;
}
else if...
}
}
I know, that my for-loop will not work that way, but it's the basic idea how I want to do it.
I have put all my PictureBoxes into my list and in the for-loop I want to count them. First it should play as long as the list is long, then every time it goes to the next box and should check the color of it and then add a one to the seperate counters. The problem is that I get errors every time and I have no idea how to read out the values of the BackColors in my list for each item.
Thank you for maybe helping me out :)

You're getting an error because the ListBox.Items collection is an ObjectCollection... it has to be, since it allows you store any object you want in it.
You'll have to cast the object back to a PictureBox before accessing properties on it:
if (((PictureBox)lstBox.Items[i]).BackColor == Color.White)
{
counterWhite += 1;
}
Or you could switch to a foreach loop and cast them all at once (using LINQ):
foreach (var pBox in new lstBox.Items.Cast<PictureBox>())
{
if (pBox.BackColor == Color.White)
{
counterWhite += 1;
}
...
}
Don't use a ListBox control as temporary storage for referencing your PictureBox controls though. You could create a List<PictureBox> to store references in, and then you won't have to cast when you iterate through the collection.
Or better yet (and the one I'd choose), just query the controls on your Form and count the number of controls of type "PictureBox" that have the BackColor you're looking for (using LINQ again).
var counterWhite = Controls.OfType<PictureBox>()
.Count(p => p.BackColor == Color.White);
var counterGreen = Controls.OfType<PictureBox>()
.Count(p => p.BackColor == Color.Green);

Related

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);
}
}

How to update text in a listview without clearing it

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.

Code generation using loop

I am new to C# programming. I have number of text boxes on a form and instead of writing the same code for each text box, is it possible to use loop for writing same code for each text box? In the code below can we use the variable i for the same?
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 3; i++)
{
MessageBox.Show(amountTextBox.Text);
MessageBox.Show(amountTextBox1.Text);
MessageBox.Show(amountTextBox2.Text);
MessageBox.Show(amountTextBox3.Text);
}
}
Thanks in advance.
Instead of trying to generate code (which is a lot harder than it sounds), whenever you have variables named something1, something2, … somethingN, you should consider using an array, or some other collection.
Create an array of text boxes, like this:
var amountTextBoxes = new[] { amountTextBox, amountTextBox1, amountTextBox2, amountTextBox3 };
And then loop through them like this:
for (int i = 0; i < amountTextBoxes.Length; i++)
{
MessageBox.Show(amountTextBoxes[i].Text);
}
Or like this:
foreach (var textBox in amountTextBoxes)
{
MessageBox.Show(textBox.Text);
}
Another option that would probably work in this specific case, (though this solution is not as general as the previous one) involves directly searching for the controls based on the name. If this is a Windows Forms application, you could use Find (assuming all controls have the same name pattern):
for (int i = 0; i < 3; i++)
{
var controlName = "amountTextBoxes" + i;
vat textBox = (TextBox)this.Controls.Find(controlName, true)[0];
MessageBox.Show(textBox.Text);
}
Or if this is WPF, you could use FindName:
for (int i = 0; i < 3; i++)
{
var controlName = "amountTextBoxes" + i;
vat textBox = (TextBox)this.FindName(controlName);
MessageBox.Show(textBox.Text);
}
You can try something like this
foreach (Control x in this.Controls)
{
if (x is TextBox)
{
MessageBox.Show(((TextBox)x).Text);
}
}
Better version with OfType extension method:
foreach (TextBox x in this.Controls.OfType<TextBox>().OrderBy(x => x.Name))
{
MessageBox.Show(x.Text);
}
Here is a linq version.
this.Controls
.OfType<TextBox>()
.ToList()
.ForEach(control =>
MessageBox.Show(control.Text));
Instead of using for loop you can do like this also..
*note * - This is for all the textbox controls in the form.. for specific textboxes you may use a for loop which loop through an array or collection of text boxes you need .
foreach(Control control in this.Controls)
{
if(control.GetType() == typeof(TextBox))
{
MessageBox.Show(control.Text);
}
}
As Selman22 mentioned you can solved it using this.Controls.OfType(). You can further refine your query as mentioned below
foreach (TextBox textBox in this.Controls.OfType<TextBox>().Where(x => x.Name.Contains("amountTextBox")).OrderBy(x => x.Name).ToList())
{
MessageBox.Show(textBox.Text);
}
A simple and maintainable way would be to yield return every textbox you're interested in, in the order that you're interested in, and then iterate over that.
private void button1_Click(object sender, EventArgs e)
{
foreach (var textBox in GetAmountTextBoxes())
MessageBox.Show(textBox.Text);
}
private IEnumerable<TextBox> GetAmountTextBoxes()
{
yield return amountTextBox;
yield return amountTextBox1;
yield return amountTextBox2;
yield return amountTextBox3;
}
If you have many textboxes and maintaining this list is cumbersome, you could generalise it to something like:
return Controls.OfType<TextBox>();
But take care to filter it, if necessary, to only the textbox controls you need with Where.
And if order is important, you'll need to devise some function to sort the textboxes (in conjunction with OrderBy). This is necessary because the default sorting algorithm will sort TextBox10 before TextBox1.
Your Code Rewritten:
for (int i = 0; i < 3; i++)
{
MessageBox.Show(amountTextBox[i].Text);
}

How do you add EmptySpaceItems and LayoutControlItems with equal widths to a LayoutControlGroup with DevExpress?

NOTE: All controls described in this question are DEVEXPRESS controls.
I am attempting to programatically add both LayoutControlItems and EmptySpaceItems to a LayoutControlGroup on a DevExpress LayoutControl.
The requirements for my project require that I have a LayoutControlGroup panel that is dependent on a set of filter items chosen from another control on the layout control. If there are no filters chosen, than none of the `LayoutControlItems are shown. If one or more of the filters are chosen that I add one or more of the controls to the group based on the selection.
The way that I attempt to do this is the following:
1) in the designer for the LayoutControl I have already created the LayoutControlGroup with the LayoutControlItems. There are 6 total, and each LayoutControlItem contains a PanelControl that contains a ListBoxControl
2) When the form is initalized I hide each of the LayoutControlItems from the LayoutControlGroup using the LayoutControlItem.HideFromCustomization() method.
3) After the user selects a filter or set of filters I run the following code attempting to restore the control items to the group from left to right.
this.layoutGroupScenarioPortfolios.BeginUpdate();
LayoutControlItem layoutControlToAdd;
LayoutControlItem lastLayoutControlItem = null;
for (int loop = 0; loop < selectedFilters.Length; loop++)
{
layoutControlToAdd = LayoutControlItemFactoryUtil(selectedFilters[loop]);
if (layoutControlToAdd == null)
{
continue;
}
if (loop < 1)
{
layoutControlToAdd.RestoreFromCustomization(this.layoutControlGroupSelectedFilters);
}
else
{
layoutControlToAdd.RestoreFromCustomization(lastLayoutControlItem, DevExpress.XtraLayout.Utils.InsertType.Right);
}
lastLayoutControlItem = layoutControlToAdd;
}
for (int loop = 0; loop < numOfEmptySpaceItemsNeeded; loop++)
{
layoutControlToAdd = new EmptySpaceItem(this.layoutControlGroupSelectedFilters)
{
Owner = this.layoutControlGroupSelectedFilters.Owner
};
layoutControlToAdd.RestoreFromCustomization(lastLayoutControlItem, DevExpress.XtraLayout.Utils.InsertType.Right);
lastLayoutControlItem = layoutControlToAdd;
}
this.layoutControlGroupSelectedFilters.TextVisible = true;
this.layoutGroupScenarioPortfolios.EndUpdate();
As you can see from the code one loop adds the appropriate ListControlBox to the group. The second loop tries to add empty space items to make sure that the list box control does not take up the entire group. By the end of this code there should be 6 items spanning the group control, each with the same width in the control.
The problem is that the first control added takes up half of the group box's space while the other 5 items are equally fitted into the remaining half of the group box.
In the first loop, is the RestoreFromCustomization() method with one parameter the correct method to use?
I would suggest to place the controls at runtime. LayoutControl will manage the LayoutControlGroups and EmptySpaceItems.
Here is the code I had written to place user controls into the LayoutControl at runtime:
LayoutControlItem lastItem = null;
int RowWidth = 0;
public void AddMyControl()
{
MyControl myControl = new MyControl("");
myControl.Name = Guid.NewGuid().ToString();
LayoutControlItem item = new LayoutControlItem();
item.Name = Guid.NewGuid().ToString();
item.Text = "";
MyLayoutControl.BeginUpdate();
//We need to determine where to insert the new item. Right or Below. If there is
//space on the right we insert at Right else we just add the item.
if(lastItem == null || lastItem != null && (MyLayoutControl.Width - UserControlWidth) < RowWidth)
{
MyLayoutControl.AddItem(item);
RowWidth = item.MinSize.Width;
}
else
{
MyLayoutControl.AddItem(item, lastItem, DevExpress.XtraLayout.Utils.InsertType.Right);
}
item.Control = myControl;
RowWidth += item.MinSize.Width;
lastItem = item;
item.Name = " ";
MyLayoutControl.BestFit();
MyLayoutControl.EndUpdate();
}
If you just need left to right controls, flowlayoutpanel will suit better. Sometimes LayoutControl is tough to work with. I had eventually go with flowlayoutpanel as it is much simpler to work with.

How to sort ListView items when ListView is in VirtualMode?

I realize i have to sort the collection where the ListView gathers the items from:
ListView listCollection = new ListView();
But this doesn't seem to work unless the ListView is added as a GUI-control to the form, that in turn makes it very slow to add items to, hence why i have to use VirtualMode on my GUI-ListView in the first place.
Anyone know how to go about this or point me in the right direction?
basically, you will need to apply sort to the data pump itself.
I did a quick search on Google for listview sort virtualmode. First result was this page, where the above quote was taken from.
For example, if your datasource is a DataView, apply sorting on this instead of the ListView.
If it is just a question of performance when adding items, I would do as barism suggests; use BeginUpdate/EndUpdate instead of VirtualMode.
try {
listView1.BeginUpdate();
// add items
}
finally {
listView1.EndUpdate();
}
If you are using virtual mode, you have to sort your underlying data source. As you may have found, ListViewItemSorter does nothing for virtual lists.
If you are using a non-virtual listview, you can also use AddRange(), which is significantly faster than a series of Add() -- In addition to using BeginUpdate/EndUpdate that has already been described.
ObjectListView (an open source wrapper around .NET WinForms ListView) already uses all these techniques to make itself fast. It is a big improvement over a normal ListView. It supports both normal mode and virtual mode listviews, and make them both much easier to use. For example, sorting is handled completely automatically.
I had the same problem switching VirtualMode True with an existing project, but the solution was surprisingly easy:
First step I am populating a list of ListViewItem, instead of ListView.Items collection:
private List<ListViewItem> _ListViewItems;
Then I have implemented the RetrieveVirtualItem method
private void mLV_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
e.Item = _ListViewItems[e.ItemIndex];
}
Finally I am sorting my list of ListViewItem using the same class I was using before, I had just only to change the base class
_ListViewItems.Sort((System.Collections.Generic.IComparer<ListViewItem>)new ListViewItemComparer(new int[] { e.Column }, mLV.Sorting));
This is my IComparer class implementation:
class ListViewItemComparer : System.Collections.Generic.IComparer<ListViewItem>
{
int[] mColonne;
private System.Windows.Forms.SortOrder order;
public ListViewItemComparer(int[] mCols)
{
this.mColonne = mCols;
this.order = System.Windows.Forms.SortOrder.Ascending;
}
public ListViewItemComparer(int[] mCols, System.Windows.Forms.SortOrder order)
{
this.mColonne = mCols;
this.order = order;
}
public int Compare(ListViewItem x, ListViewItem y)
{
int returnVal = -1;
foreach (int mColonna in mColonne)
{
double mNum1;
double mNum2;
String mStr1 = "";
String mStr2 = "";
if ((x.SubItems[mColonna].Text == "NULL") && (x.SubItems[mColonna].ForeColor == Color.Red))
{
mStr1 = "-1";
}
else
{
mStr1 = x.SubItems[mColonna].Text;
}
if ((y.SubItems[mColonna].Text == "NULL") && (y.SubItems[mColonna].ForeColor == Color.Red))
{
mStr2 = "-1";
}
else
{
mStr2 = y.SubItems[mColonna].Text;
}
if ((double.TryParse(mStr1, out mNum1) == true) && (double.TryParse(mStr2, out mNum2) == true))
{
if (mNum1 == mNum2)
{
returnVal = 0;
}
else if (mNum1 > mNum2)
{
returnVal = 1;
}
else
{
returnVal = -1;
}
}
else if ((double.TryParse(mStr1, out mNum1) == true) && (double.TryParse(mStr2, out mNum2) == false))
{
returnVal = -1;
}
else if ((double.TryParse(mStr1, out mNum1) == false) && (double.TryParse(mStr2, out mNum2) == true))
{
returnVal = 1;
}
else
{
returnVal = String.Compare(mStr1, mStr2);
}
if (returnVal != 0)
{
break;
}
}
// Determine whether the sort order is descending.
if (order == System.Windows.Forms.SortOrder.Descending)
{
// Invert the value returned by String.Compare.
returnVal *= -1;
}
return returnVal;
}
}
Hope this will help you.
Did you try beginupdate() and endupdate()? Adding data is much faster when you use beginupdate/endupdate.(when you call beginupdate, listview doesn't draw until you call endupdate)
listView1.BeginUpdate();
for (int i = 0; i < 20000; i++)
{
listView1.Items.Add("abdc", 1);
}
listView1.EndUpdate();
For very large lists, the Virtual Mode ListView is the answer for certain. In non-virtual mode it seems to draw the entire list and then clip it to the view, while in Virtual mode it simply draws the ones in the view. In my case the list was 40K+ records. In non-virtual mode an update to the ListView could take minutes. In virtual mode it was instantaneous.
To sort the list, you must sort the underlying datasource, as has already been mentioned. That is the easy part. You will also need to force a refresh to the display, which is not done automatically. You can use ListView.TopItem.Index to find the index in the underlying data source that corresponds to the top row of the Virtual ListView before the sort. There is also an API call that returns the number of display rows in the ListView that you can implement as C# function, like this:
public const Int32 LVM_GETCOUNTPERPAGE = 0x1040;
public static int GetListViewRows( ListView xoView )
{
return (int)WindowsMessage.SendMessage( xoView.Handle, LVM_GETCOUNTPERPAGE, 0, 0 );
}
That will let you calculate the range you have to update. About the only remaining question is how you reconcile the existing display with what will appear after the data have been sorted. If you want to leave the same data element in the top row, you must have some mechanism in place that will find its new index in the newly sorted list, so you can replace it in the top position - something essentially equivalent to an SQL IDENTITY.

Categories