I am using Visual Studio to create a Windows Forms C# project and am trying to set up an array of a type class, and have the entries in the array correspond to the constructor string for the class. I am using an array with an index of a variable, which increases each time that a new class instance is added to the array.
I am running into the problem of the index call is outside the bounds of the array. Additionally, I am not sure that my class variables are being set for each instance. Can anyone point me in the right direction? Below is my code:
public partial class MainMenu : Form
{
//int that will be used to alter the index of the array
public static int acctcount = 1;
//array of class Account
Account[] accounts = new Account[acctcount];
public MainMenu()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//check through each element of the array
for (int i = 0; i < accounts.Length; i++)
{
string stringToCheck = textBox1.Text;
foreach(Account x in accounts)
{
//check to see if entered name matches any element in the array
if (x.Name == stringToCheck)
{
//set variables in another form so that we are using the class variables for only that class
Variables1.selectedAccount = x.Name;
//is this calling the CheckBalance of the instance?
Variables1.selectedCheckBalance = Account.CheckBalance;
//same thing?
Variables1.selectedSaveBalance = Account.SaveBalance;
//switch to form
AccountMenu acctMenu = new AccountMenu();
this.Hide();
acctMenu.Show();
}
else
{
/*insert new instance of Account
the index element should be 0, since acctcount is set to 1
and we are subtracting 1 from the acctcount
we are using the string from the textbox1.Text as the constructor
for the new instance*/
accounts [acctcount-1] = new Account(stringToCheck);
//increase the index of the array by 1
acctcount += 1;
}
}
}
}
}
class Account
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
private static int acctNum = 0;
public static int AcctNumber
{
get
{
return acctNum;
}
set
{
acctNum = value;
}
}
//initialize the CheckBalance value to 100.00
private static decimal checkBalance = 100.00M;
public static decimal CheckBalance
{
get
{
return checkBalance;
}
set
{
checkBalance = value;
}
}
public Account(string Name)
{
this.Name = Name;
}
private static decimal saveBalance = 100.00M;
public static decimal SaveBalance
{
get
{
return saveBalance;
}
set
{
saveBalance = value;
}
}
}
The problem with the reported exception is [most likely] the line accounts[acctcount-1] as it will throw an IndexOutOfBounds exception when acctcount is >= 2 (eg. accounts[1]), as happens after the first button click and increment of acctcount. The array however, only has one element as it was created with accounts = new Account[acctcount]; - arrays in C# do not grow/resize.
The simplest and best immediate fix is to use a List (see Collections (C#)) instead of an array; Lists can grow dynamically. Then the code becomes:
// (remove the "acctcount" field as it is no longer useful)
List<Account> accounts = new List<Account>();
// ..
accounts.Add(new Account(stringToCheck));
As pointed out by Trevor, remove the static modifier in the Accounts class; otherwise the member data will be incorrectly shared (ie. each account will have the same balances!) and "overwrite" each other. If the use of static is an attempt to "pass back" the data from the form see How to return a value from a Form in C#? for a more viable solution. The same use of a public property can be used to pass an (Account) object into a form.
The exception is thrown when the button is clicked more than once.
You created an array of size 1, the second time you click the button and it tries to add an element at index 2, the index is already out of bounds.
Arrays do not grow in size as you add new items.
As pointed out, you should use a collection, like List<T>
If you wanted to keep using arrays, everytime you add a new item, you need to create a new array of bigger size, copy the elements of the old array to the new array, and reference the old array to the new array. You can also create an array of a bigger size and only create a new array when it's full. Which is basically what the .Net collections already implement.
As always, it all depends on what your needs and requirements are.
Related
Have in mind that this problem probably is the consequence of a beginners-mistake.
There are there 4 classes in my program relevant for this problem.
Main form: Declares and initiates the object currentRecipe (uses class recipe) and recipeMngr (uses class recipeManager) directly in the class.
currentRecipe(Recipe) have a couple of fields that is collected from user input, the fields include the array ingredients[] that is collected from a property in another form that opens when a button is pressed. currentRecipe is used later when "add recipe button" is pressed and because it is used in both these methods the object currentRecipe needs to be initialized outside these methods as i understand it.
RecipeManager: Hold an array that stores the recipes. And method that that manages the adding of the recipe into the array. this method takes currentRecipe as a property from the main form.
Recipe: Holds the template for Recipe.
FormIngredients: collect ingredients from user and stores them in property.
However, the problem. When storing an recipe in the array in recipeManager the latest stored array just copies so all earlier assigned items in the list gets the new value. As an result
recipe[0] = Waffles
recipe[1] =
...
when adding a new it becomes
recipe[0] = pie
recipe[1] = pie
...
When it should become
recipe[0] = Waffles
recipe[1] = pie
...
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitializeGUI();
}
private const int maxRecipe = 20;
private const int maxIngredients = 50;
private static Recipe currentRecipe = new Recipe(maxIngredients);
private static RecipeManager recipeMngr = new RecipeManager(maxRecipe);
private void btnAddIngredients_Click(object sender, EventArgs e)
{
FormIngredients FormI = new FormIngredients(currentRecipe);
var result = FormI.ShowDialog();//show ingredient form
if (result == DialogResult.OK) {
currentRecipe = FormI.recipe;
lblIngredAmount.Text = "Ingredients: " + currentRecipe.ingredientsAmounts().ToString();
}
}
private void AddRecipe(Recipe scurrentRecipe) {
scurrentRecipe.rName = tbxName.Text;
scurrentRecipe.rDescription = tbxDescription.Text;
scurrentRecipe.rCategory = (FoodCategories)cbxCategory.SelectedIndex;
bool valid = checkIfValid();
if (valid)
{
recipeMngr.Add(scurrentRecipe);
updateGUI();
SimpleInitializeGUI();
}
else
{
MessageBox.Show("Please fill all the fields (or add atleast one ingredient)", "Stop");
}
}
I've added the most important parts from main form, where is know the problem is from.
I want to add that the problem disappears when moving the initialization of currentRecipe into the "addRecipe click"-method. But this creates alot of new problems and the code is supposed to be structured like this.
I do not use any loop to fill the array.
Explanation:
Classes are reference types. If you go through the original code you've posted, the currentRecipe is instantiated only once. Everytime you are changing the value of scurrentRecipe / currentRecipe variables, you are just changing that object only. If you need multiple objects, you've to create them by multiple times by using the new keyword.
Updated code to make it clear for you:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitializeGUI();
}
private const int maxRecipe = 20;
private const int maxIngredients = 50;
private static Recipe currentRecipe = new Recipe(maxIngredients);
private static RecipeManager recipeMngr = new RecipeManager(maxRecipe);
private void btnAddIngredients_Click(object sender, EventArgs e)
{
FormIngredients FormI = new FormIngredients(currentRecipe);
var result = FormI.ShowDialog();//show ingredient form
if (result == DialogResult.OK) {
currentRecipe = FormI.recipe;
lblIngredAmount.Text = "Ingredients: " + currentRecipe.ingredientsAmounts().ToString();
}
}
private void AddRecipe() {
scurrentRecipe = new Recipe(maxIngredients);
scurrentRecipe.rName = tbxName.Text;
scurrentRecipe.rDescription = tbxDescription.Text;
scurrentRecipe.rCategory = (FoodCategories)cbxCategory.SelectedIndex;
bool valid = checkIfValid();
if (valid)
{
recipeMngr.Add(scurrentRecipe);
updateGUI();
SimpleInitializeGUI();
currentRecipe = scurrentRecipe();
}
else
{
MessageBox.Show("Please fill all the fields (or add atleast one ingredient)", "Stop");
}
}
I'm creating a simple distance calculator. I have a window form with 2 combo boxes. The first combobox has the default place which is the Place1 and the second combobox has the value of Place1,Place2 and Place3.
Here's my code in my class.
public class classDistance
{
public string Places { get; set; }
public int DistanceKm { get; set; }
public static void Hours() {
List<string> Cities = new List<string>();
Cities.Add("Place1");
Cities.Add("Place2");
Cities.Add("Place3");
List<int> DestinationKm = new List<int>();
DestinationKm.Add(10); // Place1 = 10Km
DestinationKm.Add(20); // Place2 = 20Km
DestinationKm.Add(30); // Place3 = 30Km
return;
}
}
//Place1 has the speed of 80kmph.
And when the user click the button in the Form, it will calculate the Hours from the method class classDistance
// Inside my form
public Form1()
{
InitializeComponent();
}
classDistance classname = new classDistance();
private void button1_Click(object sender, EventArgs e)
{
if(comboBox1.Text == "Place1"){
// call the method on the classname if the user click the button and calculate the hours
}
}
How can I do that?
Thank you in advance!
As you made method Hours() static, you can simply call it by the class without creating any instance, like that:
private void button1_Click(object sender, EventArgs e)
{
ClassDistance.Hours();
}
But now your method just fills the lists, not calculates hours actually.
UPD:
I think it's better off using dictionary for storing and getting data in your case:
var citiesDistance = new Dictionary<string, int>
{
{"Place1", 10},
{"Place2", 20},
{"Place3", 30}
};
Then, you can get appropriate value by the place's name, e.g.:
var name = "Place2";
var length = citiesDistance[name];
And after that, you can calculate hours, needed to get to the place:
var speed = 80;
var hours = length/speed;
Hope, I've understood your question correctly)
At the moment you have a list of places and a list of distances which aren't really related to each other.
What I would do is create a new class which contains both the place name and distance, e.g.:
public class Destination
{
public string Name {get;set;}
public int DistanceKm {get;set;}
// ctor
public Destination(String name, int distanceKm)
{
Name = name;
DistanceKm = distanceKm;
}
public int getHours (int speed)
{
// do your calculation for the number of hours here
return hours;
}
}
You can then populate the destination with it's name and distance via the constructor, and use the getHours method to return the number of hours based on the speed you pass into it.
You could also create a List if you were handling multiple destinations
This question already has an answer here:
How do I save an ArrayList to the application settings
(1 answer)
Closed 2 years ago.
I am writing a simple application - a kind of saper game in winforms in C#. My application already saves some information in application settings like the size or colour of buttons , but when i try to save an arraylist of my own structure I get a problem. The are no errors but the information is not saved for the next program execution.Ustawienia is a public static class including wyniki which is another form , and Properties.Settings.Default.scores is an ArrayList added in application settigs. I would be grateful if you have any idea what i am doing wrong and how to store the arraylist in app settings.
Here is the code:
public partial class Form3 : Form
{
public Form3()
{
InitializeComponent();
if (Properties.Settings.Default.scores == null)
Properties.Settings.Default.scores = new System.Collections.ArrayList();
}
private void ok_click(object sender, EventArgs e)
{
Highscore higscore = new Highscore(Properties.Settings.Default.ktory, textBox1.Text, ustawienia.ile_wierszy, ustawienia.ile_kolumn, ustawienia.elapsed.Seconds);
Properties.Settings.Default.scores.Add( higscore);
Properties.Settings.Default.scores.Sort(new myComparer());
Properties.Settings.Default.ktory++;
Properties.Settings.Default.Save();
Highscore.show();
this.Close();
}
}
public class Highscore
{
public int nubmer;//w properties ktory=+1;
public string name;
int rows;
int columns;
public int time;
public Highscore(int _number, string _name, int _rows, int _columns, int _time)
{
number = _number;
name = _name;
rows = _rows;
columns = _columns;
time = _time;
}
public static void show()
{
ListView list = (ListView)ustawienia.wyniki.Controls.Find("listView1", true)[0];
list.Items.Clear();
foreach (Highscore e in Properties.Settings.Default.scores)
{
ListViewItem newItem = new ListViewItem(new[] { e.name, e.time.ToString(), e.rows.ToString()+"x"+e.columns.ToString() });
lista.Items.Add(newItem);
}
ustawienia.wyniki.Show();
}
}
public class myComparer:IComparer
{
int IComparer.Compare(Object x, Object y)
{
if (((Highscore)x).time < ((Highscore)y).time)
return 1;
else if (((Highscore)x).time > ((Highscore)y).time)
return -1;
else
{
return String.Compare(((Highscore)x).name,((Highscore)y).name);
}
}
}
}
In my experience, it was always a problem with trying to use the default Settings. For example, on Windows 8 the Default settings was saved in this folder pattern:
C:\Users\[username]\AppData\Local\[AssemblyName]\[AssemblyName].exe_Url_[random_string_of_characters]\1.0.0.0\user.config
However, in debug mode it would be .vhost.exe and then when testing locally it would be just .exe. Then if the version increased it would change the path again.
Eventually I gave up on trying to figure out which Settings file was being used, and trying to force it to save to a different (cleaner) folder. Instead, I switched to using a custom settings class and using XmlSerializer. It's not worth the headache.
I have the following ViewModel and in my code link this to an items control. However when I use a line such as:
onOffSchedule.dc.schedules[0].days[0].data[0] = 9;
it only updates the UI sometimes?? And when it does it updates the data all wrong. Instead of assign the first schedule, the first day, and first time slot to 9. It updates the first schedule (for all days??) for the first time slot.
So I am trying to figure out why it updates every index in the days array instead of just the first one.
Thanks in advance!
public class ScheduleVM
{
public ObservableCollection<Schedule> schedules { get; set; }
private static ScheduleVM viewModel = null;
public static ScheduleVM getInstance()
{
if (viewModel == null)
viewModel = new ScheduleVM();
return viewModel;
}
private ScheduleVM()
{
schedules = new ObservableCollection<Schedule>();
for (byte i = 0; i < 32; i++)
schedules.Add(new Schedule());
}
}
public class Schedule
{
public ObservableCollection<Day> days { get; set; }
public Schedule()
{
days = new ObservableCollection<Day>();
int[] values = new int[96];
for (byte i = 0; i < 96; i++)
values[i] = 3;
for (byte i = 0; i < 8; i++)
days.Add(new Day() { data = values });
}
}
public class Day : BaseVM
{
private int[] _data;
public int[] data
{
get
{
return _data;
}
set
{
_data = value;
OnPropertyChanged("data");
}
}
}
Below is the view that goes along with this code. It is a user control that I create inside another window (and inside that window the user control is called 'onOffSchedule'.
public ScheduleVM dc { get; private set; }
public Schedule()
{
InitializeComponent();
dc = ScheduleVM.getInstance();
this.DataContext = dc;
schedule.ItemsSource = dc.schedules[0].days;
}
onOffSchedule.dc.schedules[0].days[0].data[0] = 9;
That line will not trigger an update under normal circumstances. What you're doing is just setting an integer in an integer array:
public int[] data { ... }
You put the OnPropertyChanged call in there for when the entire array changes. But when you just set one of the values, WPF doesn't know that occurred.
You have the right idea by using the ObservableCollection<Day> on your other class. You need to do something similar in your Day class for the data property.
You're updating an element within the data array. That array does not fire off property changed notifications, so there's no way that the View knows to update itself. Make your data an ObservableCollection and see if it helps.
Long story short, I needed a set of objects with dictionary-like functionality that can be serialized in order to save user data. The original dictionary was a Dictionary class that held an array of Item objects and the amounts of each object 'held' by the user. After finding some recommendations on the internet I tried implmenting my own dictionary-like class from KeyedCollection, but can't seem to add objects to it. Am I adding the objects wrong or is something wrong with my collection?
The 'SerialDictionary' class:
public class SerialDictionary : KeyedCollection<Item, int>
{
protected override int GetKeyForItem(Item target)
{
return target.Key;
}
}
public class Item
{
private int index;
private string attribute;
public Item(int i, string a)
{
index = i;
attribute = a;
}
public int Key
{
get { return index; }
set { index = value; }
}
public string Attribute
{
get { return attribute; }
set { attribute = value; }
}
}
The Main form (that is trying to add the object)
public partial class Form1 : Form
{
SerialDictionary ItemList;
Item orb;
public Form1()
{
InitializeComponent();
ItemList = new SerialDictionary();
orb = new Item(0001, "It wants your lunch!");
orb.Key = 001;
}
private void button1_Click(object sender, EventArgs e)
{
ItemList.Add(orb);
}
}
The error I am receiving when trying to add an object:
The best overloaded method match for 'System.Collections.ObjectModel.Collection.Add(int)' has some invalid arguments
If I throw an int in there it compiles, but I'm trying to get a collection of the Item objects in there...
You have it backwards, it should be:
public class SerialDictionary : KeyedCollection<int, Item>
The key type comes first in the signature, then the item type.