I have a list of windows but it is not in the order I want them. I'm able to get the windows into string from the title - they are being put into a list of windows. I want to sort this list in a specific order with Estimate 1st, Control Center 2nd, and Login 3rd. This is the order I desire. I have know idea on how to go about it but I want to sort it before it goes into the foreach loop.
private void CloseMainWindows(IEnumerable<Window> Windows)
{
var winList = Windows.ToList();
winList.Sort()//This is where I want to sort the list.
foreach (Window window in winList)
{
if (window.Title.Contains("Estimate"))
{
Estimate.closeEstimateWindow();
}
if (window.Title.Contains("Control Center"))
{
ContorlCenter.CloseContorlCenter();
}
if (window.Title.Contains("Login"))
{
login.ClickCanel();
}
}
}
One way would be to have a lookup function:
int GetTitleIndex(string s)
{
if (s.Contains("Estimate")) return 0;
if (s.Contains("Control Center")) return 1;
if (s.Contains("Login")) return 2;
}
Then, to sort, you lookup the indexes:
winList.Sort((x, y) => GetTitleIndex(x).CompareTo(GetTitleIndex(y)));
Alternatively, you could create the list directly using LINQ's OrderBy:
var winList = Windows.OrderBy(GetTitleIndex).ToList();
And in fact in your case you don't even need the intermediate list:
foreach (var window in Windows.OrderBy(GetTitleIndex))
{
...
}
You can do something like this :
List<Type> data = new List<Type>();
data.Sort(new Comparison<Type>(Compare));
private static int Compare(Type x, Type y)
{
//you can compare them like so :
//I'll show you an example just for the sake of illustrating how :
if(x.Name.ToString().Length > y.Name.ToString().Length) return 1;
else return -1;
//the logic for the comparison is up to you.
//compare the 2 elements.
}
Related
Hopefully this post gives more clarity as to what I am trying to achieve.
Objective: I want to spawn 20 apples(that have an attached button) from a list at runtime. When the apples are clicked they will spawn a popup with information pertaining to the apple that was clicked.
What I'm doing currently: I am using a for loop to run through the list to spawn the apples. I currently have the following code:
public class AppleInventory : MonoBehaviour
{
[SerializeField] private ApplesScript applPrefab;
[SerializeField] private Transform applParent;
public ApplesScript CreateApples()
{
var appl = Instantiate(applPrefab, applParent);
for (int i = 0; i < apples.Count; i++)
{
appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
}
return appl;
}
}
The Problem: The problem is that when I use the for loop and click on the button,it returns the following error: ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. The popup information also does not update.
Code without for loop: The code works to spawn one apple when I remove the for loop and set the int i = to a specific number, like below. It will give the correct popup info for any number that "i" is set to. This lets me know that it is not the rest of the code that is the issue. This leads me to believe it is the "return" line along with the for loop that is the issue. It seems I may need to "return" for each iteration but I am unsure of how to go about doing this.
public ApplesScript CreateApples()
{
int i = 7;
var appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
return appl;
}
Thank you,
-
UPDATE
The fix was so simple. I just ended up creating a new method specifically for the for loop and it worked the way I wanted. My code now looks like this:
public void StarterOfApplesCreation()
{
for (int i = 0; i < apples.Count; i++)
{
CreateApples(i);
}
}
public void CreateApples(int i)
{
var appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
}
You have two options. The conventional option is to create all the items first and then return them all in some sort of list, e.g.
public static void Main()
{
foreach (var thing in GetThings(5))
{
Console.WriteLine(thing.Number);
}
Console.ReadLine();
}
public static Thing[] GetThings(int count)
{
var things = new Thing[count];
for (var i = 0; i < count; i++)
{
things[i] = new Thing { Number = i };
}
return things;
}
The more modern option is to use an iterator. It actually will return one item at a time. It has the limitation that you have to use the items there and then - you won't have random access like you would an array or the like - but it also has advantages, e.g.
public static void Main()
{
foreach (var thing in GetThings(5))
{
Console.WriteLine(thing.Number);
}
Console.ReadLine();
}
public static IEnumerable<Thing> GetThings(int count)
{
for (var i = 0; i < count; i++)
{
var thing = new Thing { Number = i };
yield return thing;
}
}
The result of an iterator will usually be used as the source for a foreach loop or a LINQ query. Note that you can always call ToArray or ToList on the result of an iterator if you do want random access in specific situations, but you still have the advantages of an iterator elsewhere. For instance, let's say that your method produces 1000 items and you want to find the first one that matches some condition. Using my first example, you would have to create all 1000 items every time, even if the first one was a match. Using an iterator, because the items are processed as they are created, you can abort the process as soon as you find a match, meaning that you won't unnecessarily create the remaining items.
Note that my examples use the following class:
public class Thing
{
public int Number { get; set; }
}
You can copy and paste the code into a Console app that doesn't use top-level statements. The bones of the code will still work with top-level statements, but you'll need to make a few other modifications.
Store each separate "appl" that gets instantiated in an Array, ie appls[i]=appl
Do this within the for loop.
If you think about it, by putting the line "return appl;" outside the for loop, you are only storing that last game object, not all of them. Thats why creating an array of gameobjects and assigning them within the loop may work for you.
I'm trying to make a leaderboard in Windows forms using c# but I can't come up with a solution.
Here is my current code.
lstleaderboard.Items.Add(int.Parse(txtScore.Text));
ArrayList Sorting = new ArrayList();
foreach (var o in lstleaderboard.Items)
{
Sorting.Add(o);
}
Sorting.Sort(new ReverseSort());
lstleaderboard.Items.Clear();
foreach (var o in Sorting)
{
lstleaderboard.Items.Add(o);
}
And I tried altering the code like this:
lstleaderboard.Items.Add(int.Parse(txtScore.Text));
ArrayList Sorting = new ArrayList();
foreach (var o in lstleaderboard.Items)
{
Sorting.Add(o);
}
Sorting.Sort(new ReverseSort());
lstleaderboard.Items.Clear();
foreach (var o in Sorting)
{
lstleaderboard.Items.Add(o + txtName.Text );
}
if (lstleaderboard.Items.Count == 11)
{
lstleaderboard.Items.RemoveAt(lstleaderboard.Items.Count - 1);
}
but this did not really work since it was then also sorting the names which messed up the scoreboard and it showed the wrong results, number one could be number three for example.
I am still learning so I apologize if my question is silly or my code is "weird"
Thanks
also here is my sorting class:
public class ReverseSort : IComparer
{
public int Compare(object x, object y)
{
return Comparer.Default.Compare(y, x);
}
}
So lstLeaderBoard is just list of integers, so you can safely use integer comparison, the way you did it, it just uses some default comparer on object type, which is far away from integer.
So simple LINQ OrderBy would suffice:
lstLeaderBoard.Items.Add(int.Parse(txtScore.Text));
var sortedItems = lstLeaderBoard.Items.Cast<int>().OrderBy(x => x);
lstLeaderBoard.Items.Clear();
foreach (var item in sortedItems)
lstLeaderBoard.Items.Add(item);
I want to iterate over a custom list i.e. defined as:
List<CurrentCluster> _curClusters = new List<CurrentCluster>();
IEnumerator<CurrentCluster> _clusIterate = _curClusters.GetEnumerator();
while (_clusIterate.MoveNext())
{
// Error_01: Cannot implicitly convert CurrentCluster to Cluster
Cluster _curClus = _clusIterate.Current; // Cluster is base class while
// CurrentCluster is derived class
// Error_02: Does not contain a definition for GetClusterSize()
if (_curClus.GetClusterSize() == 0)
{
// Error_03: Remove(char) has some invalid arguments.
_clusIterate.ToString().ToList().Remove(_curClus);
}
}
while method GetClusterSize() is defined in class Cluster.cs as:
public int GetClusterSize()
{
return _clusterObjects.Count;
// _clusterObjects is a defined in this class as:
// List<EvoObject> _clusterObjects = new List<EvoObject>();
}
If the size of specific cluster is equal to zero in that cluster list (i.e. _curClusters then to remove that cluster from the list.
How can we iterate over a custom list and remove item from list conditionally?
How about just using List RemoveAll method and doing this?
_curClusters.RemoveAll(_curClus=>_curClus.GetClusterSize() == 0);
You should be able to use a for loop - you have to work backwards because otherwise you would be moving the elements and some would get skipped.
for (int n=_curClusters.Count; n>=0; n--)
{
if (_curClusters[n].GetClusterSize()==0)
{
_curClusters.RemoveAt(n);
}
}
Removing items from a collection using iteration is both advanced and obsolete technique. Use LINQ instead:
_curClusters = _curClusters.Where(c => c.GetClusterSize() > 0).ToList();
Now curClusters contains just "sized clusters", whatever that means.
If you insist to do it through iterations this is the way:
The catch is that you MUST NOT change a collection while iterating over its items. Instead, you can iterate and determine if an item needs to be deleted and mark it somehow - for instance you can add it to another list which contains only items to be deleted. After the first iteration over the original collection, start second one and remove the items from the original, like so:
var toBeRemoved = new List<CurrentCluster>();
foreach (var suspiciousCluster in _curCluseters)
{
if(suspiciousCluster.GetClusterSize() == 0)
{
toBeRemoved.Add(suspiciousCluster);
}
}
foreach (var voidCluser in toBeRemoved)
{
_curCluster.Remove(voidCluster);
}
Again, _curClusters contains just "sized clusters", whatever this might mean.
However I highly recommend the first approach.
I did not understand why you are going with that complexity ... simply you can achieve the goal by below code
List<CurrentCluster> _curClusters = new List<CurrentCluster>();
_curClusters.RemoveAll(i => i.GetClusterSize()== 0);
//OR
for (int i = 0; i < _curClusters.Count; )
{
//If you have some more logical checking with CurrentCluster
//before remove
if (_curClusters[i].GetClusterSize()== 0)
{
_curClusters.Remove(_curClusters[i]);
continue;
}
i++;
}
I need to iterate over a list (or whatever enumeration), but I'd like to add values into the list in the course of the iteration.
This is an example.
public static void RunSnippet()
{
List<int> hello = new List<int>();
hello.Add(1); hello.Add(2); hello.Add(3);
foreach (var x in hello)
{
Console.WriteLine(x);
if (x == 1) {
hello.Add(100);
}
}
}
I expect to get "1,2,3,100", but instead I got this error.
How can I iterate over a list that is changing in the process?
ADDED
What I want to accomplish is that I iterate over elements to process something. The thing is that some of the elements needs to be decomposed into sub elements on and on.
public static void RunSnippet()
{
List<Element> hello = new List<Element>();
hello.Add(Element); hello.Add(Element); hello.Add(Element);
foreach (var x in hello)
{
List<Element> decomposed;
decomposed = Decompose(x);
if (decomposed != null) {
foreach (var y in decomposed)
{
hello.Add(y);
}
}
}
}
You can't, basically. Not with a foreach loop, anyway. You can use a straight for loop:
for (int i = 0; i < hello.Count; i++)
{
int x = hello[i];
Console.WriteLine(x);
if (x == 1) {
hello.Add(100);
}
}
I would personally try to avoid doing it in the first place though - it can get very hard to reason about whether you'll ever complete, or if you'll skip items (if you're removing instead of adding, or adding before your current position).
You can't. You should create a new list and store the values in there.
public static void RunSnippet()
{
List<int> hello = new List<int>();
List<int> additions = new List<int>();
hello.Add(1); hello.Add(2); hello.Add(3);
foreach (var x in hello)
{
Console.WriteLine(x);
if (x == 1) {
additions.Add(100);
}
}
hello.AddRange(additions);
}
Use a snapshot of it instead:
foreach (var x in hello.ToArray())
{
// whatever here
}
Problem solved! Well, in a way. Items added during iteration would not be included.
No, you can't iterate over a list and modify them in the same iteration. Use a new list instead.
I found that there is stack in C#. I guess I could use stack.
public static void RunSnippet()
{
Stack<int> hello = new Stack<int>();
hello.Push(1); hello.Push(2); hello.Push(3);
while (hello.Count > 0)
{
int x = hello.Pop();
Console.WriteLine(x);
if (x == 1) {
hello.Push(100);
}
}
}
Using foreach you can't! You could use a for-loop, but it's very very bad style to do things like this. Things like this make your code very error prone, unpredictable and hard to debug.
There are answers that claims what you want cannot be achieved with foreach. That claim is wrong, all you need to do is to write a custom class with a custom enumerator.
public class CustomList : IEnumerable<int>
{
readonly List<int> list = new List<int>{1,2,3,4};
private int now = 0;
public void Add(int n)
{
list.Add(n);
}
public IEnumerator<int> GetEnumerator()
{
while (now<list.Count)
{
yield return list[now];
now++;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Now the following piece of code will print 1,2,3,4 and 100 to the screen:
var list = new CustomList();
foreach (int n in list)
{
if(n==1)
list.Add(100);
Console.WriteLine(n);
}
But I write this only as a proof of concept. You don't want to do this. If you will only add new items on the back, use Queue as others has said. If you will always add new items on the front, use Stack. If you will need both, write a custom LinkedList class with Dequeue (=Pop), Enqueue and Push operations, use something like :
while(list.notEmpty())
var item = list.Dequeue();
//bla bla
and you are all set. (You could even write a custom Enumerator again, to use with foreach, but we are destructing the list as we go, so it is against the spirit of Enumerations, and why bother in any case)
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.