I'm developing a Silverlight 3 app and getting this really weird error when I try to add an object to a Canvas. My code is as follows:
for (int i = 0; i < person.Children.Count; i++)
{
//Add children in same position as parent
Person child = person.Children[i];
child.x_PositionTransform.X = person.x_PositionTransform.X;
child.x_PositionTransform.Y = person.x_PositionTransform.Y;
child.Click += new RoutedEventHandler(person_Click);
x_LayoutRoot.Children.Add(child);
}
The first time I use this, it works as expected. However, when I hit x_LayoutRoot.Children.Add(child) after clicking a Person object that was created using this code, I get an ArgumentException telling me that "Value does not fall within the expected range."
However, when I add the following code before adding child to x_LayoutRoot.Children, the problem disappears.
child.SetValue(Canvas.NameProperty, "child" + objCount++);
Why is this happening? Is this a Silverlight bug, or (more likely) am I just missing something?
I think I've figured out the cause of this: I was adding multiple Person objects with the same name. So, if anyone has this issue in the future, make sure all of your objects have unique names!
Person child = person.Children[i];
child.x_PositionTransform.X = person.x_PositionTransform.X;
child.x_PositionTransform.Y = person.x_PositionTransform.Y;
child.Click += new RoutedEventHandler(person_Click);
child.SetValue(Canvas.NameProperty, "child" + objCount++); //!
x_LayoutRoot.Children.Add(child);
you can add different NameProperty before add a child
Related
I am trying to populate a panel with a prefab when the player has an upgrade available however I am getting an error with my current code and in the console i am getting the following error
InvalidOperationException: Collection was modified; enumeration operation may not execute.
The following code block is causing this error
GameObject currentTable = upgradeTable[upgradeTableLevel - 1];
if(GameManager.Instance.AvailableUpgrades != null){
foreach(Upgrade upg in GameManager.Instance.AvailableUpgrades){
GameObject go = Instantiate(upgradePrefab,currentTable.transform);
go.GetComponentInChildren<Text>().text = upg.ToString() + "(level: " + GameManager.Instance.upgrades[upg] + ")";
go.GetComponentInChildren<Button>().GetComponentInChildren<Text>().text = "Buy for " + GameManager.Instance.GetNextCost(GameManager.Instance.upgrades[upg]).ToString() + " units";
GameManager.Instance.DisplayedUpgrades.Add(upg);
GameManager.Instance.AvailableUpgrades.Remove(upg);
}
}
This code is also spawning multiple of the prefab
The goal behavior was for the List "AvailableUpgrades" to be populated with upgrades when they are available which is done with this piece of code
public void UpdateAvailableUpgrades(){
foreach (KeyValuePair<Upgrade,int> cur in upgrades) {
if (units >= GetNextCost(cur.Value)) {
if (!AvailableUpgrades.Contains (cur.Key) && !DisplayedUpgrades.Contains(cur.Key)) {
AvailableUpgrades.Add (cur.Key);
}
}
}
}
Once an upgrade is available this code block is called
#region UpgradeTable
int upgradeTableLevel = GameManager.Instance.FetchUpgradeLevel(Upgrade.UpgradeTable);
upgradeTable[upgradeTableLevel - 1].SetActive(true);
foreach(GameObject go in upgradeTable){
if(go != upgradeTable[upgradeTableLevel - 1]){
go.SetActive(false);
}
}
//TODO
//Add a way to find the currently active table and add new upgrades prefab there
//TEMP SOLUTION
GameObject currentTable = upgradeTable[upgradeTableLevel - 1];
if(GameManager.Instance.AvailableUpgrades != null){
foreach(Upgrade upg in GameManager.Instance.AvailableUpgrades){
GameObject go = Instantiate(upgradePrefab,currentTable.transform);
go.GetComponentInChildren<Text>().text = upg.ToString() + "(level: " + GameManager.Instance.upgrades[upg] + ")";
go.GetComponentInChildren<Button>().GetComponentInChildren<Text>().text = "Buy for " + GameManager.Instance.GetNextCost(GameManager.Instance.upgrades[upg]).ToString() + " units";
GameManager.Instance.DisplayedUpgrades.Add(upg);
GameManager.Instance.AvailableUpgrades.Remove(upg);
}
}
#endregion
Whats happening here is im getting the table level(needed for grabbing the currently active table and other unrelated stuff) once we get down to //temp solution I am grabbing the currently active table. Then checking to see if there are any upgrades in the AvailableUgrades if so I loop through the list to instantiate a new upgrade prefab for each upgrade in the list at the end of this list the upgrade is removed from AvailableUpgrades and added to DisplayedUpgrades I added 2 list for handling this to solve the issue of it constantly spawning new prefabs but it is still spawning them
So how do I fix my script to provide the expected behavior?
The error explains clearly, the collection you are enumerating here;
foreach(Upgrade upg in GameManager.Instance.AvailableUpgrades){
...
Is then modified here, which is still inside the loop (enumeration);
GameManager.Instance.AvailableUpgrades.Remove(upg);
}
You simply need to instead maintain a list of upgrades to remove, and then remove them after the loop is finished.
So make a new list of upgrades, change the .Remove to add it to this new list instead [upgradesToRemove.Add(upg)], and then after the loop is finished you can remove all the upgrades in the new list from the GameManager.Instance.AvailableUpgrades, since it is no longer being enumerated when you are outside the loop. Ez.
As mentioned by Milney, foreach loops do not allow you to alter the enumeration being iterated for safety reasons. It is usually a good practice to avoid this sort of operation anyway since it can easily lead to nasty bugs. If you wish to do it anyway (as I have done it myself a few times), you can do it with for loops instead of foreach:
for (int i = 0; i < GameManager.Instance.AvailableUpgrades.Count; i++) {
// Do you thing
if (/*some possible condition*/) {
GameManager.Instance.DisplayedUpgrades.Add(/*some new Object*/);
}
if (/*another possible condition*/) {
GameManager.Instance.AvailableUpgrades.RemoveAt(i);
i--; // Since i is pointing to current object that was just removed
// Otherwise you'd skip one object
}
}
I repeat. This kind of code can easily lead to hard-to-track bugs and is also hard to understand for newcomers in your project so recommend you try to avoid messing with the enumeration during the loop when possible.
Edit: Or... since I noticed your code does not check for conditions to remove each object you could simply call GameManager.Instance.AvailableUpgrades.Clear() after the loop to remove all objects.
I'm working on a small program as part of my A Level Computing course that is designed to track orders. It is written in C# using the Windows Forms.
I am having an issue where I enter all the information for a new order and then press OK and it should update the ListView with the information. I have my ListView in Detail view with 4 columns but nothing ever gets added to the ListView. The section of code that should add the items to the ListView is being executed and is not throwing any errors or causing the program to crash but nothing is being added. Its weird because I am using the exact same method that I used in my little prototype mock up but for some reason now it is not working.
All the things I've found on here or on the internet seem to suggest its an issue with the View mode of the ListView and I've tried modifying this property to no avail.
Any ideas why this section of code is refusing to add anything to the ListView?
//Create an array to store the data to be added to the listbox
string[] orderDetails = { Convert.ToString(id + 1), rNameBox.Text, dateBox.Value.ToString(), orderBox.Text };
//DEBUGGING
Console.WriteLine(orderDetails[0]);
Console.WriteLine(orderDetails[1]);
Console.WriteLine(orderDetails[2]);
Console.WriteLine(orderDetails[3]);
//END DEBUGGING
//Add the order info to the ListView item on the main form
var listViewItem = new ListViewItem(orderDetails);
ths.listView1.Items.Add(listViewItem);
If you need any more information just say. Apologies if this is in the wrong format or something this is my first time here.
Your problem is that your ListViewItem contains a string array and it has no useful way of displaying it.
What you should be doing (there are a number of ways of doing this, but here's one) is creating a class, OrderDetail, with an Id, a Name, a Date, and so on. Give it a ToString() method (public override string ToString()) which returns what you want to display, e.g.:
public override string ToString()
{
return this.Name;
}
Create an instance of OrderDetail and set its properties. Create ListViewItem giving it the OrderDetail instance and add to the ListView. Repeat for as many OrderDetail instances you want.
Cheers -
Added: code which works:
int id = 12;
string rNameBoxText = "rName";
DateTime dateBoxValue = DateTime.Now;
string orderBoxText = "order";
string[] orderDetails = { Convert.ToString(id + 1), rNameBoxText, dateBoxValue.ToString(), orderBoxText };
//DEBUGGING
Console.WriteLine(orderDetails[0]);
Console.WriteLine(orderDetails[1]);
Console.WriteLine(orderDetails[2]);
Console.WriteLine(orderDetails[3]);
//END DEBUGGING
this.listView1.Columns.Clear();
this.listView1.Columns.Add("Id");
this.listView1.Columns.Add("rName");
this.listView1.Columns.Add("Date");
this.listView1.Columns.Add("Order");
this.listView1.View = View.Details;
//Add the order info to the ListView item on the main form
var listViewItem = new ListViewItem(orderDetails);
this.listView1.Items.Add(listViewItem);
I've found similar answers to my question before, but not quite to what I'm trying to do...
In Visual Basic (last I used it, in 06/07) there was an "Index" property you could assign to multiple controls with the same name. I used this primarily to loop through controls, i.e.:
For i = 1 to 500
picSeat(i).Print "Hello"
Next i
Is there a way to do this in C#? I know there is a .IndexOf(), but would that really help for what I'm doing? I want to have multiple controls with the same name, just different index.
This is a Windows Form Application, and I'm using Visual Studio 2012. I am talking about controls, not arrays/lists; this was possible in VB and I was wondering if it was possible at all in C#. So I want to have, say, 30 seats in a theatre. I want to have each seat represented by a picturebox named "picSeat". VB would let me name several objects the exact same, and would assign a value to a control property "Index". That way, I could use the above loop to print "Hello" in every picture box with only 3 lines of code.
No, this feature does not exist in C#, and was never implemented in the transition from classic VB to VB.Net.
What I normally do instead is put each of the controls in question in a common parent container. The Form itself can work, but if you need to distinguish these from others of the same type a GroupBox or Panel control will work, too. Then, you access the controls like this:
foreach (var picBox in parentControl.Controls.OfType<PictureBox>())
{
// do something with each picturebox
}
If you want to use a specific control, just write by name:
pictureBox6.SomeProperty = someValue;
If you need to change a specific control determined at run-time, normally this is in response to a user event:
void PictureBox_Click(object sender, EventArgs e)
{
var picBox = sender As PictureBox;
if (picBox == null) return;
//picBox is now whichever box was clicked
// (assuming you set all your pictureboxes to use this handler)
}
If you really really want the Control Arrays feature, you can do it by adding code to create the array to your form's Load event:
PictureBox[] pictureBoxes = Me.Controls.OfType<PictureBox>().ToArray();
Are we talking WinForms here? I'm not sure, but I don't think you can have multiple controls in winforms with same name. But I vaguely recall doing something similar and the solution was to name them Button_1, Button_2 etc. Then you can iterate through all controls and get your own index.
Beware though that if you want to instanciate a separate control for each seat in a theatre, you might run into some serious performance issues :) I've done something similar to that as well and ended up drawing the whole thing on a canvas and using mouse coordinates to handle the events correctly.
You may want to check out the Uid property of controls.
(http://msdn.microsoft.com/en-us/library/system.windows.uielement.uid(v=vs.110).aspx)
You can access Control through Uid property with the following
private static UIElement FindUid(this DependencyObject parent, string uid)
{
var count = VisualTreeHelper.GetChildrenCount(parent);
if (count == 0) return null;
for (int i = 0; i < count; i++)
{
var el = VisualTreeHelper.GetChild(parent, i) as UIElement;
if (el == null) continue;
if (el.Uid == uid) return el;
el = el.FindUid(uid);
if (el != null) return el;
}
return null;
}
And simply use
var control = FindUid("someUid");
I copied code from this post
If you create an indexed dictionary of your user control, it will behave pretty much the same as in VB6, though you'll not see it on the VS C# GUI. You'll have to get around the placement issues manually. Still - and most importantly -, you'll be able to refer to any instance by the index.
The following example is for 3 pieces for clarity, but of course you could automate every step of the process with appropriate loops.
public partial class Form1 : Form
{
...
Dictionary<int, UserControl1> NameOfUserControlInstance = new Dictionary<int, UserControl1>()
{
{ 1, new UserControl1 {}},
{ 2, new UserControl1 {}},
{ 3, new UserControl1 {}}
};
private void Form1_Load(object sender, EventArgs e)
{
NameOfUserControlInstance[1].Location = new System.Drawing.Point(0, 0);
NameOfUserControlInstance[2].Location = new System.Drawing.Point(200, 0);
NameOfUserControlInstance[3].Location = new System.Drawing.Point(400, 0);
Controls.Add(NameOfUserControlInstance[1]);
Controls.Add(NameOfUserControlInstance[2]);
Controls.Add(NameOfUserControlInstance[3]);
}
...
}
I like using Tags to apply any type of meta data about the controls
for (int i = 0; i< 10; ++i)
{
Button button = new Button();
button.Tag = i;
}
I am working on creating buttons that are created dynamically based the results of a SQL Query:
private void createPagingButtons(DateTime firstDayofWeek, DateTime lastDayofWeek)
{
int i = 1;
SqlDataReader returnedQuery = getDefaultUser(firstDayofWeek, lastDayofWeek);
while (returnedQuery.Read())
{
string buttonName = returnedQuery["Person"].ToString();
System.Diagnostics.Debug.WriteLine(buttonName);
Button btn = new Button();
btn.ID = i.ToString();
btn.Click += new EventHandler(btn_Click);
btn.Text = buttonName;
pagingPanel.Controls.Add(btn);
i++;
}
}
The way I am trying to assign unique button ID's is by assigning them a number that is incremented each time the while loop iterates:
btn.ID = i.ToString();
But it's not working and I am getting an error:
Multiple controls with the same ID '1' were found. FindControl requires that controls have unique IDs.
WHy is this happening and how can I fix it?
The only way this could occur is if this method were executed more than one time or if these buttons are somehow persisted during the load and then you call it again. However, dynamic controls are for all intents and purposes not persisted and must be recreated on each post back to be useful.
Thus my conclusion, you're calling it more than once.
As other users said, this error will be thrown if the function is called more than once. Your function will re-start at one each time it is called. Which will duplicate the IDs.
Assuming this is actually the desired behavior, that your function can be called multiple times and will create new buttons each time. Making your int i = 1; a class level variable would resolve this.
Replace i++ with ++i.. This should solve your issue..
I have a list of strings that I loop throu, and then add them in a Accordion. When I've added all of them I want the last item to expand. The code looks like this:
for (int i = 0; i < ivDialogList.Count; i++)
{
AccordionItem ai = new AccordionItem();
ai.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
ai.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch;
ai.Content = ivDialogList[i].Message;
ai.Header = ivDialogList[i].PostType + " " + ivDialogList[i].User + " " + ivDialogList[i].PostDate;
if (i == ivDialogList.Count - 1)
ai.IsSelected = true;
content.Items.Add(ai);
}
This is working fine, but as soon as I click on any of the other accordion items or close the last one, I get an out of range exception. Does any body have another way of doing this or know the reason why I get the exception and can help.
Thanks
This works fine for me using "Lore Ipsum.." text, the toolkit Accordion and a bunch of AccordionItems - the problem must be in some of your code that you are not showing here. Can you post a stacktrace and the XAML?
I managed to solve the question. I think the problem was somewhere in the loop, and probably specific for my code. What I did was that I moved the:
if (i == ivDialogList.Count - 1)
ai.IsSelected = true;
part in the loop out, so it would be set after the loop is done, like this:
((AccordionItem)content.Items[ivDialogList.Count - 1]).IsSelected = true;
And that made it work like a charm.