So I have this code in a .cs file called SchoolData that has a class and a list.
public static List<YearGroupsData> yearGroupsDataList = new List<YearGroupsData>();
public class YearGroupsData
{
public int id { get; set; }
public int year { get; set; }
public string groupName { get; set; }
public int subject { get; set; }
}
However, I'm trying to use a loop in another .cs script that does a web connection and gets the data from the website, I haven't included the connection info or some of the script for that as this isn't the part going wrong...
private IEnumerator ViewYearGroups()
{
//Some code for connection here
yield return viewYearGroups;
string yearGroupsDataString = viewYearGroups.text;
yearGroups = yearGroupsDataString.Split(';');
foreach (string yearGroup in yearGroups)
{
YearGroupsData yearGroupsData = new YearGroupsData()
{
id = Int32.Parse(GetDataValue(yearGroup, "Id:")),
year = Int32.Parse(GetDataValue(yearGroup, "Year:")),
groupName = GetDataValue(yearGroup, "GroupName:"),
subject = Int32.Parse(GetDataValue(yearGroup, "Subject:")),
};
SchoolData.yearGroupsDataList.Add(yearGroupsData);
}
}
The GetDataValue is the part that is messing up. It gives me ArgumentOutOfRangeException and I'm not sure why. It works if I'm not using it in a loop, I've tried a for loop as well and still the same, anyone know what's happening?
public string GetDataValue(string data, string index)
{
string value = data.Substring(data.IndexOf(index) + index.Length);
if (value.Contains("|"))
{
value = value.Remove(value.IndexOf("|"));
}
return value;
}
Add a try catch in your GetDataValue() method to help with debugging. If it works without the foreach loop, then my guess is one of the string objects you are iterating over is different than what you may be expecting.
https://msdn.microsoft.com/en-us/library/system.argumentoutofrangeexception(v=vs.110).aspx
try
{
string value = data.Substring(data.IndexOf(index) + index.Length);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine(e.Message);
}
Where I used the .Split to divide the string at each semicolon was the issue. The string I was splitting had a space after the last semicolon which was creating an empty index from that. I used
yearGroups = yearGroups.Take(yearGroups.Count() - 1).ToArray();
to remove the last index that was empty. The trycatch method helped me find this, thanks.
the ArgumentOutOfRangeException happen when the value of an argument is outside the allowable range of values as defined by the invoked method
try this:
string value = data.Substring(data.IndexOf(index) + index.Length - 1 );
Related
I'm working on my first real c# project and I have faced a problem with my way of creating List based on a Class, which I have no idea how to solve.
I’m trying to write some code, which takes an input file (txt/csv) of multiple constructions with multiple layers, put it into my program, and later write the constructions into a new txt/csv file.
When having the same numbers of layers, it works fine. But when the constructions have different numbers of layers it causes trouble and I get a “System.IndexOutOfRangeException”.
My question is: Can I make the Class which I’m basing my List on, dynamic (I don’t know if it is the technical term), so it work with different numbers of inputs? Both when Adding the construction to the program and when I write it to a new file?
My code is:
class Program
{
static void Main(string[] args)
{
// Filepath for the input and output file
string filePathIn_constructions = #"C:\Library\Constructions.txt";
string filePathOut = #"C:\Library\EPlus_Inputfile.txt";
// Creating a list of constructions based on the class. The list is made from the file "filePathIn_constructions"
List<Construction> allConstructions = new List<Construction>();
List<string> lines_constructions = File.ReadAllLines(filePathIn_constructions).ToList(); // add it to a list
// Adding all the data from the fil to the variable "allConstructions"
foreach (var line in lines_constructions)
{
string[] entries = line.Split(',');
Construction newConstruction = new Construction();
newConstruction.EIndex = entries[0];
newConstruction.Name = entries[1];
newConstruction.Layer1 = entries[2];
newConstruction.Layer2 = entries[3];
newConstruction.Layer3 = entries[4];
newConstruction.Layer4 = entries[5];
newConstruction.Layer5 = entries[6];
allConstructions.Add(newConstruction); // Add it to our list of constructions
}
List<string> output = new List<string>();
foreach (var x in allConstructions) // Printing the new
{
output.Add($"{x.EIndex}, {x.Name}, {x.Layer1}, {x.Layer2}, {x.Layer3}, {x.Layer4}, {x.Layer5}");
}
File.WriteAllLines(txtFilePathOut, output);
}
}
My Class for the Constructions is
public class Construction
{
public string EIndex { get; set; }
public string Name { get; set; }
public string Layer1 { get; set; }
public string Layer2 { get; set; }
public string Layer3 { get; set; }
public string Layer4 { get; set; }
public string Layer5 { get; set; }
}
An example of a input/output file could be
Construction,ConcreteWall,Concrete;
Construction,Brickwall1,Birck,Isulation,Brick;
Construction,Brickwall2,Birck,AirGap,Isulation,Brick;
Construction,Wood/Concrete Wall,Wood,Isulation,Concrete,Gypson;
Construction,Wood Wall,Wood,AirGap,Gypson,Isulaiton,Gypson;
I hope someone can help. Thanks.
Edit: I have to be able to excess the construction Name seperatly, because i'm using it to do some sorting of the.
public class Construction
{
public string EIndex { get; set; }
public string Name { get; set; }
public List<string> Layers { get; set; } = new List<string>();
}
foreach (var line in lines_constructions)
{
string[] entries = line.Split(',');
Construction newConstruction = new Construction();
newConstruction.EIndex = entries[0];
newConstruction.Name = entries[1];
for (int i=2; i < entries.Length; i++) {
newConstruction.Layers.Add(entries[i]);
}
allConstructions.Add(newConstruction);
}
foreach(var x in allConstuctions) {
File.AppendAllText(output, $"{x.EIndex}, {x.Name}, {string.Join(", ", x.Layers)}");
}
It is because you are trying to reach a cell of an array that doesn't exist (documentation)
In your input/output file you have lines that have between 3 and 7 values, and you are building an array entries out of those values. This means that you will have arrays with between 3 and 7 cells
The problem is that right after creating those arrays you try to access on every array the cells 0, 1, 2... up to the 7th, even for arrays that have only 3 cells!
What you could do to fix this in a simple way is to add columns to have the same number of separator on each lines (you defined the separator of your lines as column with line.Split(',')). This way, every arrays that you will create will always have 7 cells, even if the value inside is null
This is best I could make the question statement. Please be kind.
Here is the situation:
I have a string "InputValues" which contains values in comma seperated format:
chkAwareness1,chkAwareness2,chkAwareness6,chkAwareness9,chkAwareness13...
I need to fill an object with bool value if the name matches with what I have in above string variable.
example:
if InputValues contains "chkAwareness1" then "public bool chkAwareness1" should set to true, otherwise false.
public class SurveyCheckBox
{
public bool chkAwareness1 { get; set; }
public bool chkAwareness2 { get; set; }
public bool chkAwareness3 { get; set; }
public bool chkAwareness4 { get; set; }
public bool chkAwareness5 { get; set; }
public bool chkAwareness6 { get; set; }
public bool chkAwareness7 { get; set; }
.
.
.
}
public void createObjectSurveyCheckBox(string InputValues)
{
string[] ChkValues = InputValues.Split(',');
SurveyCheckBox surveyChkBoxObj = new SurveyCheckBox();
for (int i = 0; i < NumberOfPropertyInSurveyCheckBox ;i++ )
{
// typeof(SurveyCheckBox).GetProperties()[i].Name
}
}
I searched and I found GetProperties method through which I can get the name of property, but I am unable to figure out the logic.. how to search through the values and assign them to bool properties.
Please help.
You're very close. You just need to change your loop, really. The whole method should look like this:
public void CreateObjectSurveyCheckBox(string inputValues)
{
string[] chkValues = inputValues.Split(',');
SurveyCheckBox surveyChkBoxObj = new SurveyCheckBox();
foreach (string value in chkValues)
{
PropertyInfo propInfo = typeof(SurveyCheckBox).GetProperty(value);
if (propInfo != null)
propInfo.SetValue(surveyChkBoxObj, true);
}
}
P.S. You'll notice I took the liberty of changing your capitalization to something much more standard. If you use capitalization like you had, you're likely to get lynched.
I agree with Tim; I would not use something like this in production code.
public void createObjectSurveyCheckBox(string InputValues)
{
var instance = new SurveyCheckBox();
foreach (var property in typeof(SurveyCheckBox).GetProperties().Where(x => x.Name.Contains("chkAwareness")))
{
if (InputValues.Contains(property.Name))
property.SetValue(instance, true);
}
}
I would write the loop from the other direction, from 0 to MaxchkAwareness;
Sort the input first, before going into the loop.
You would also need an index to the next item in your input array (ChkValues), lets call that chkValueIndex;
If the next item in your input array, ChkValues[chkValueIndex], is "chkAwareness"+i.ToString()
then your property is true, and you increment your array pointer .
otherwise your property is false.
But I think you have to use reflection to set the properties in a loop like that, something like this:
Getting a property reference using reflection
I am sure there are better ways to restructure this and do it entirely different, but it sounds to me like you are trying to do the best you can with the system that was given you.
You can try this:
public static void createObjectSurveyCheckBox(string InputValues)
{
string[] ChkValues = InputValues.Split(',');
SurveyCheckBox surveyChkBoxObj = new SurveyCheckBox();
foreach (var prop in typeof(SurveyCheckBox).GetProperties())
{
if (ChkValues.Contains(prop.Name))
prop.SetValue(surveyChkBoxObj, true);
}
}
Basically i have the user open a text document that is formatted like this currently.
Burger.jpg,Double Down KFC,Food,30/06/95,This is a burger
it then splits the info into an array then into variables and then into text boxes.
obviously if i wanted multiple records i may have to format it differently, (thats what i need help with)
But if i had it like this what would be the most efficient way of taking these records from the text file and storing them separately so i can flick through them. For example with a combo box on my form. When the record is selected the form populates with that records data.
multiple records:
Burger.jpg,Double Down KFC,Food,30/06/95,This is a burger
Person.jpg,Smile,People,23/06/95,This is a Person
Here is my code currently for this part.
private void LoadFile()
{
StreamReader reader = new StreamReader(fileName);
content = reader.ReadLine();
doc = content.Split(',');
filename = Convert.ToString(doc[0]);
fileNameTextBox.Text = doc[0];
description = doc[1];
descriptionTextBox.Text = doc[1];
category = doc[2];
categoryComboBox.Text = doc[2];
//dateTaken = Convert.ToDouble(doc[3]);
dateTakenTextBox.Text = doc[3];
comments = doc[4];
commentsTextBox.Text = doc[4];
}
This code currently works but only for the first record as it is using one array, and i obviously will need multiple ways of storing the other lines.
I Think the best option if i was going to give it a guess would be to use a List of some sort with a Class that generates Records, but that is where i am stuck and need help.
(usually my questions on here get downvoted as i am not concise enough if that is the case comment and i will try to alter my question.
Thanks everyone.
I would create a class that holds the information of a record
public class ImageInfo
{
public string FileName { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public DateTime Date { get; set; }
public string Comments { get; set; }
public override string ToString()
{
return FileName;
}
}
Now you can write a method that returns the image infos
public List<ImageInfo> ReadImageInfos(string fileName)
{
string[] records = File.ReadAllLines(fileName);
var images = new List<ImageInfo>(records.Length);
foreach (string record in records) {
string[] columns = record.Split(',');
if (columns.Length >= 5) {
var imageInfo = new ImageInfo();
imageInfo.FileName = columns[0];
imageInfo.Description = columns[1];
imageInfo.Category = columns[2];
DateTime d;
if (DateTime.TryParseExact(columns[3], "dd/MM/yy",
CultureInfo.InvariantCulture, DateTimeStyles.None, out d))
{
imageInfo.Date = d;
}
imageInfo.Comments = columns[4];
images.Add(imageInfo);
}
}
return images;
}
Now you can fill the textboxes with one of these records like this
List<ImageInfo> images = ReadImageInfos(fileName);
if (images.Count > 0) {
ImageInfo image = images[0];
fileNameTextBox.Text = image.FileName;
descriptionTextBox.Text = image.Description;
categoryComboBox.Text = image.Category;
dateTakenTextBox.Text = image.Date.ToShortDateString();
commentsTextBox.Text = image.Comments;
}
The advantage of this approach is that the two operations of reading and displaying the records are separate. This makes it easier to understand and modify the code.
You can add ImageInfo objects to a ComboBox or ListBox directly instead of adding file names if you override the ToString method in the ImageInfo class.
public override string ToString()
{
return FileName;
}
Add the items to a combo box like this:
myComboBox.Items.Add(image); // Where image is of type ImageInfo.
You can retrieve the currently selected item with:
ImageInfo image = (ImageInfo)myComboBox.SelectedItem;
Most likely you will be doing this in the SelectedIndexChanged event handler.
void myComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
ImageInfo image = (ImageInfo)myComboBox.SelectedItem;
myTextBox.Text = image.FileName;
}
Create a class that resembles a row of your data, then iterate over the file, making your split and constructing a new class instance with your split data. Store this in a List<> (or some other appropriate structure) ensure you store it such that it can be referenced later. Don't change your UI as you are loading and parsing the file (as Mike has suggested), also as mike suggests you need to read until the EOF is reached (plenty of examples of this on the web) MSDN example.
Also, streamreader implements IDisposable, so you need to dispose of it, or wrap it in a using statement to clean up.
Example class, you could even pass the row in as a constructor argument:
public class LineItem
{
public string FileName { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public DateTime DateTaken { get; set; }
public string Comments { get; set; }
public LineItem(string textRow)
{
if (!string.IsNullOrEmpty(textRow) && textRow.Contains(','))
{
string[] parts = textRow.Split(',');
if (parts.Length == 5)
{
// correct length
FileName = parts[0];
Description = parts[1];
Category = parts[2];
Comments = parts[4];
// this needs some work
DateTime dateTaken = new DateTime();
if (DateTime.TryParse(parts[3], out dateTaken))
{
DateTaken = dateTaken;
}
}
}
}
}
Your code is not iterating through the records within the file.
You want to continue reading until the end of the file.
while (content != eof)
{
// split content
// populate text boxes
}
But this will overwrite your text boxes with each pass of the loop.
Also, you want to separate your code - do not mix I/O process with code that updates the UI.
The name of the method implies you are loading a file, but the method is doing far more than that. I would suggest changing the method to read the file, split each record into a class object which then gets stored into an array - and return that array.
A separate method will take that array and populate your table or grid or whatever is in the UI. Ideally, you have the gridview bind to the array.
If you keep all your entries the same:
name,food,type,blah blah
name,food,type,blah blah
you can add another split into your code:
line = content.Split('\n');
foreach (line in filename)
{
doc = line.Split(',');
//do stuff...
As for the option for string multiple entries, a method I have used is implementing a list of Models:
class ModelName
{
string Name { get; set; }
string foodType { get; set; }
//etc...
public void ModelName()
{
Name = null;
foodType = null;
//etc...
}
}
List<Model> ModelList;
foreach (line in filename)
{
doc = line.Split(',');
Model.Name = doc[1];
//etc...
And have a different list, and a different Model for each type of entry (person or food)
With my program I'm trying to automatize another program of which there can be multiple instances. I've already written functionality that will watch the processlist and detect all processes of the program that I want to automatize.
It will store some basic informations about found instances into this ConcurrentDictionary which has its ProcessId as key and the class ProgramToWatch as value:
public static ConcurrentDictionary<int, ProgramToWatch> ProgramToWatchDictionary = new ConcurrentDictionary<int, ProgramToWatch>();
public class ProgramToWatch
{
public string ListItemName { get; set; }
public Process BEProcess { get; set; }
public string BEMainWindowTitle { get; set; }
public Application BEApplication { get; set; }
public Window BEWindow { get; set; }
public bool isLoggedIn { get; set; }
public List<ProgramToWatchDataGrid> BEDataGrid = new List<ProgramToWatchDataGrid>();
}
Now the part I am having problems with. The program I want to watch has a DataGridView which I want to copy into my dictionary. For this I have the BEDataGrid List. The list is using this class as its type:
public class ProgramToWatchDataGrid
{
public int ListID { get; set; }
public string Name { get; set; }
public string Mail { get; set; }
}
Now to store it, I create another instance of the ProgramToWatchDataGrid (called updateGrid), write all the data I read into it, and place this into my Dictionary (I simplified). To do this, I iterate through the DataGrid (first while loop), row for row and copy the updateGrid to my Dictionary - in the second while loop I display the values to verify:
public void ReadDataGridView(int ProcessId)
{
ProgramToWatchDataGrid updateGrid = new ProgramToWatchDataGrid();
//read and store every row
int i=0;
while(i<=totalRowsInGrid)
{
updateGrid.ListId = DataGrid.Rows[i].Cells[0].Value;
updateGrid.Name = DataGrid.Rows[i].Cells[1].Value;
updateGrid.Mail = DataGrid.Rows[i].Cells[2].Value;
ProgramToWatchDictionary[ProcessID].BEDataGrid.Insert(i, updateGrid);
Display(ProgramToWatchDictionary[ProcessID].BEDataGrid[i].Mail);
}
Display("Elements: " + ProgramToWatchDictionary[ProcessID].BeDataGrid.Count);
//display every rows mail
i=0;
while(i<=totalRowsInGrid)
{
Display("HI: " + ProgramToWatchDictionary[ProcessID].BEDataGrid[i].Mail);
i++;
}
}
The way I understand it, the Information rows I read should now be located in ProgramToWatchDictionary[ProcessID].BEDataGrid[accordingRow] because I inserted the Information at that place.
The strange thing is, that this output will be produced:
[01:19] christoferlindstrm#yahoo.com
[01:19] eliseisaksson#yahoo.com
[01:19] peter#pan.com
[01:19] Elements: 3
[01:19] HI: peter#pan.com
[01:19] HI: peter#pan.com
[01:19] HI: peter#pan.com
So right after I've inserted it, the List contains the right value. But after it will always be the last value that was read? Why does this happen and how can I fix this?
Some help would be greatly appreciated!
Got it.
You should create the instance of the object to be added in the while, otherwise you are using only one instance which happens to get different values in your cycle. And of course it will end up with the last value you have inserted.
public void ReadDataGridView(int ProcessId)
{
ProgramToWatchDataGrid updateGrid = null;
//read and store every row
int i=0;
while(i<=totalRowsInGrid)
{
//create a new instance
updateGrid = new ProgramToWatchDataGrid();
updateGrid.ListId = DataGrid.Rows[i].Cells[0].Value;
updateGrid.Name = DataGrid.Rows[i].Cells[1].Value;
updateGrid.Mail = DataGrid.Rows[i].Cells[2].Value;
ProgramToWatchDictionary[ProcessID].BEDataGrid.Insert(i, updateGrid);
Display(ProgramToWatchDictionary[ProcessID].BEDataGrid[i].Mail);
}
Display("Elements: " + ProgramToWatchDictionary[ProcessID].BeDataGrid.Count);
//display every rows mail
i=0;
while(i<=totalRowsInGrid)
{
Display("HI: " + ProgramToWatchDictionary[ProcessID].BEDataGrid[i].Mail);
i++;
}
}
I have a ChaseSelection class which i use in casting my dropdown list objects,
now i am trying to put the the values from the database as a default value in the drop down list, but it does not seem to work, can anyone help? I dont even think my loop runs. Here is my chaseselection class, and also put in the loop below: Thanks
public class ChaseSelectionItems
{
public string code { get; set; }
public string text { get; set; }
public ChaseSelectionItems(string code, string text)
{
this.code = code;
this.text = text;
}
public override string ToString()
{
return this.text;
}
}
foreach (ChaseSelectionItems items in drpdwnChaseSecSelection.Items)
{
if (items.code == _Row.xcs_View)
{
drpdwnChaseSecSelection.SelectedValue = items.text;
}
}
It is not entirely clear how you configured the listbox but most likely you did not configure ValueMember correctly. The following might fix that:
foreach (ChaseSelectionItems items in drpdwnChaseSecSelection.Items)
{
if (items.code == _Row.xcs_View)
{
// drpdwnChaseSecSelection.SelectedValue = items.text;
drpdwnChaseSecSelection.SelectedItem = items;
}
}