I'm sure it's trivial but it's been a long day.
I have a DataTable in which one row has an empty cell. I want to find the row that has the empty cell, and simply assign a string to that cells value.
However when I step through the code during debug, the value never gets plugged into the table. What am I doing wrong???
currentTime = DateTime.Now.ToString();
for (int i = 0; i < OwnersTable.Rows.Count; i++)
if (OwnersTable.Rows[i].ItemArray[9].ToString() == "")
OwnersTable.Rows[i].ItemArray[9] = currentTime;
I found to accomplish this I had to create an entirely new row, copy every cell's contents of the existing row over, and then add it back to the table.
What???
Why didn't the simple cell assignment work in the code above????
The getter of the DataRow.ItemArray returns an array containing entire values for the row, modifying it's elements does not make any change to the row values.
Gets or sets all the values for this row through an array.
So you need to assign an array to it instead (use the setter), but I do not recommend this way:
currentTime = DateTime.Now.ToString();
for (int i = 0; i < OwnersTable.Rows.Count; i++) {
object[] items = OwnersTable.Rows[i].ItemArray;
if (items[9].ToString() == string.Empty) {
items[9] = currentTime
OwnersTable.Rows[i].ItemArray = items;
}
}
You can use the SetField method of DataRow instead:
currentTime = DateTime.Now.ToString();
foreach (DataRow row in OwnersTable.Rows) {
string value = row.Field<string>(9);
if (string.IsNullOrEmpty(value)) {
row.SetField<string>(9, currentTime)
}
}
Note that I assumed the field is of string type.
Just came across this post after experiencing the same problem. A more direct way to do this that bypasses the ItemArray problem Mehrzad mentions is to both test and assign values using row[columnIndex] wherever row.ItemArray[columnIndex] is used. The direct indexer of the row does not reference a copy of the row's data. The code becomes:
for (int i = 0; i < OwnersTable.Rows.Count; i++)
if (OwnersTable.Rows[i][9].ToString() == "")
OwnersTable.Rows[i][9] = currentTime;
I was facing through same issue. Apparently you don't need the key word ItemArray while assigning the value.
So instead of
OwnersTable.Rows[i].ItemArray[9] = currentTime;
You could use
OwnersTable.Rows[i][9] = currentTime;
Related
I have a battleship like terminal game, the user enters a coordinate like e2, and the program checks one of the instance variables of my object Box, it checks whether hasShip is true, if its true then it will make the coordinate e2 false, and give the output "Ship destroyed"
The problem is that all my objects are called a1,a2,a3,a4,a5,b1,b2 and so on.
I have created 25 instances of the Box class. All names as such.
Once the program gets input, either e4 ,e5 etc. I want to convert that string into an object.
For example( I want to do something like this )
target = Console.ReadLine();
target.hasShip == true;
I want to convert target into an object, then use target to use the methods of the Box class.
Because the other approach requires me to make loads of if statements, which isn't clean code, doesn't look good, and is a waste if you ask me.
Thanks in advance,
New Answer: use an Array
I am slow. I did not pay attention that you are making a battleship-like game, and that we know that the "boxes" make a rectangle. We can store this efficiently in an array.
Why I did not catch up to this fact earlier? I guess I need to wake up properly.
So, use an array:
var board = new Box[5, 5];
Now, to populate it, we can do a double for loop:
for(var indexRow = 0; indexRow < 5; indexRow++)
{
for(var indexCol = 0; indexCol < 5; indexCol++)
{
board[indexRow, indexCol] = new Box();
}
}
Note: pay attention that the indexes go from 0 to 4. For a total of 5 values: {0, 1, 2, 3, 5}.
And to query from it, we will need the indexes...
Addendum on populating the array
In comments, OP has said that each Box has an id and the ship positions are picked at random.
We can give the id in the loop:
for(var indexRow = 0; indexRow < 5; indexRow++)
{
for(var indexCol = 0; indexCol < 5; indexCol++)
{
var box = new Box();
box.vhID = (((char)(((int)'a') + indexRow))).ToString() + ((char)(((int)'1') + indexCol)).ToString();
board[indexRow, indexCol] = box;
}
}
What I am doing here is constructing the id from the indexes. Basically taking the value of 'a' and adding the indexRow will give us 'a' when indexRow is 0, 'b' when it is 1 and so on. Similarly, we get the digit that represents the column.
Note: We convert the char to int, do the addition, then convert back to char... and then from char to string. Once we have string, we can concatenate them.
I do not think we need this id. But, hey, you can do it like this.
OP also mentions that he will pick 4 ship positions at random. Fair enough:
var random = new Random();
for (var ships = 0; ships < 4; ships++)
{
board[random.Next(0, 4), random.Next(0, 4)].hasShip = true;
}
Since the user inputs an string, I suggest to create a function to convert it to the index pair:
var input = Console.ReadLine();
if (TryGetCoordinates(input, out int irow, out int icol))
{
var target = board[irow, icol];
}
else
{
Console.WriteLine("The cell {0} does not exist.", input);
}
// ...
bool TryGetCoordinates(string cell, out int indexRow, out int indexCol)
{
// ...
}
Start by validating null:
bool TryGetCoordinates(string cell, out int indexRow, out int indexCol)
{
indexRow = -1;
indexCol = -1;
if (cell == null)
{
return false;
}
// ...
}
Note: Feel free to use Trim, ToUpper or ToUpperInvariant.
We know that must be a letter followed by a digit, we can validate the length:
bool TryGetCoordinates(string cell, out int indexRow, out int indexCol)
{
indexRow = -1;
indexCol = -1;
if (cell == null)
{
return false;
}
if (cell.Length != 2)
{
return false;
}
// ...
}
We extract the characters and from them the coordinates. Noting that the first one is a letter, and the other a digit. We can also validate they are withing bounds.
bool TryGetCoordinates(string cell, out int indexRow, out int indexCol)
{
indexRow = -1;
indexCol = -1;
if (cell == null)
{
return false;
}
if (cell.Length != 2)
{
return false;
}
indexRow = (int)cell[0] - (int)'a';
indexCol = (int)cell[1] - (int)'1';
return indexRow < 5 && indexRow >= 0 && indexCol < 5 && indexCol >= 0;
}
And of course, you can do a loop of the validation similar to what was explained in the old answer.
Note: the issue with value types I describe in the old answer still applies with the array.
Old Answer: Use a Dictionary
I believe you do not want to convert the string to an object (the string is an object by the way), you want to pick the Box object you previously created based on the string. And you want to do it without using if statements. What you need is a dictionary.
So, you would have Dictionary<string, Box> meaning that it is a dictionary that you can query by string and stores Box.
Addendums:
In this case, string is the key type, by which we will access the dictionary. When we add an object to the dictionary we identify it with a key, and when we retrieve it, we also use the key. The key does not have to be string, you can choose a different type. string is convenient in this case because it is what you get from Console.ReadLine().
You can create the dictionary to store whatever type you need. If you do not need Box, you can create a dictionary that stores something else.
Creating and populating the Dictionary
Then, you add to the Dictionary all your Box objects, like this:
var dict = new Dictionary<string, Box>();
// ...
dict.Add("a1", CreateBoxA1());
Where CreateBoxA1 represents whatever means you have to create the object. No, you do not need to create a method for each Box... you can do it like this:
dict.Add("a1", new Box());
Or whatever. I do not know how you create them, so consider that a placeholder, ok? ok.
Querying and retrieving values from the Dictionary
Once you have all your Box instances in your dictionary, you can get the one you need using the string:
Console.WriteLine("Enter the name of the Box:");
var name = Console.ReadLine();
var target = dict[name];
Addendum: The value you get from dict[name] is the value that you added to the dictionary with that key. So, if the user typed "a1" it dict[name] will be the value that we added with "a1" (dict.Add("a1", new Box());). Again, if what you need is not Box you can create a dictionary to store a different type.
Input validation
You can also use the Dictionary to validate if the string corresponds to a Box that exists, for example:
Console.WriteLine("Enter the name of the Box:");
var name = Console.ReadLine();
if (dict.KeyExists(name))
{
var target = dict[name];
// ...
}
else
{
Console.WriteLine("The Box {0} does not exist", name);
}
It goes without saying, but... you can make a loop based on that, for example:
Box target = null;
while(true)
{
Console.WriteLine("Enter the name of the Box:");
var name = Console.ReadLine();
if (dict.KeyExists(name))
{
target = dict[name];
break;
}
Console.WriteLine("The Box {0} does not exist", name);
}
Also, it goes without saying, but... you can add your own validations and sanitation steps. For example using ToUpper, ToUpperInvariant or Trim. And I would remind you that changing strings to lower or upper case is culture sensitive.
See also: Best Practices for Using Strings in .NET.
Editing an removing objects from the dictionary
Once you have the object you retrieved from the Dictionary...
var target = dict[name];
We can use it, and even modify it:
var target = dict[name];
if (target.hasShip) // no need for "== true" if hasShip bool
{
target.hasShip = false;
Console.WriteLine("Ship Destroyed");
}
An special note must be done if Box is value type. For a custom type that means that it is not a class but a struct. The problem with value types is that they are copied on assignment, meaning that when you do var target = dict[name]; with a value type, you get a copy. You must then update the dictionary once you manipulated it:
var target = dict[name];
if (target.hasShip) // no need for "== true" if hasShip bool
{
target.hasShip = false;
dict[name] = target;
Console.WriteLine("Ship Destroyed");
}
Note: As I said above, this is only needed for value types.
And you can even remove the Box from the dictionary if that is necesary:
dict.Remove(name);
I am not getting value from datagrid to a variable.
I tried following code. I'm not getting any syntax or compilation error. But still code is not working.
public void get_bnamegv1()
{
for (int i = 0; i < dataGridView2.Rows.Count; ++i)
{
string textname = Convert.ToString(dataGridView2.Rows[i].Cells[1].Value);
temp_count.Text = textname;
}
}
The only reason for getting an empty string is the value from data grid view column is null, which after converting to string becomes empty string.
Can you try
dataGridView2.Rows[i].Cells[1].Value.ToString()
in place of
Convert.ToString(dataGridView2.Rows[i].Cells[1].Value);
If you get a System.NullReferenceException then you are getting null value from grid's cell.
First, if the csv file (eg: Date and Price) contains some empty cells, how can I solve this problem when I try to store the data in a list?
Second, once the two lists are okay I'd like to know how to create a 2-D object such as PriceByDate[100,2](Date[100,0],Price[100,1]) which can store the data from the csv file.
Thanks in advance
Here is my code:(Not work)
var reader = new StreamReader(File.OpenRead(#"filenames"));
List<string> Dates = new List<string>();
List<string> Prices = new List<double>();
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(';');
listA.Add(values[0]);
listB.Add(values[1]);
}
DateTime[] s = Convert.ToDateTime(ListA.toArray());
double[] o = ListB.toArray();
object[,] PriceByDate = new object[,]{{s},{o}};
This article show how to use ADO.net to read a CSV file. It is pretty simple. Using this method your Date and Price information will be on a row/record object already paired.
Article: Reading CSV files in ADO.NET
If you use the above articles solution, all you need to do is a simple string.IsNullOrEmpty test on the fields of the record. If the function returns true you can skip the row/record.
I'm not sure if this is what you are looking for as in a "2-D" object. If you need to create an object in your code to hold the data after reading it from the record I would use something like this. On a side note you may also want to use a decimal for holding monetary values.
public class PriceAndDate
{
public DateTime Date {get;set;}
public Decimal Price {get;set;}
}
List<PriceAndDate> priceAndDate = new List<PriceAndDate>();
First, a recommendation:
I would recommend using a free 3rd-party CSV library, rather than reinventing the wheel, unless there is some reason you can't.
http://joshclose.github.io/CsvHelper/
Now, the answer:
It sounds like your problem is how to handle empty cells. First, you need to make sure each row is the correct length, in a CSV you'll at least get a delimited between each cell, even if they're empty. (NOTE: I wrote all of this code long-hand without an IDE, it likely won't compile as-is, there may be errors).
var line = reader.ReadLine();
var values = line.Split(';');
if (values.Count != numColumnsExpected)
{
throw new System.Exception("Expected " + numColumnsExpected + " columns, only found " + values.Count + " columns for a row.");
}
Each column should have an expected type, you could have a validation and processing function for each column if you want to be thorough. You could map the column number to a function in a Dictionary.
private void ProcessorDelegate(string value);
Dictionary<int, ProcessorDelegate> m_processorMethods = new Dictionary<int, ProcessorDelegate>
{
{ 0, DateProcessor },
{ 1, PriceProcessor },
}
private void DateProcessor(string value)
{
// Make sure 'value' is a date
DateTime date;
if (!DateTime.TryParse(value, out date))
{
// If this field is required you could throw an exception here, or output a console error.
// This is the point at which you could check if 'value' was null or empty.
return;
}
// 'value' was a date, so add it to the DateTime[] array.
Dates.Add(date);
}
int numColumnsExpected = 6;
var Dates = new List<string>();
var Prices = new List<double>();
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(';');
if (values.Count != numColumnsExpected)
{
throw new System.Exception("Expected " + numColumnsExpected + " columns, only found " + values.Count + " columns for a row.");
}
// Sanity check, you must have a processor for each column
if (values.Count > m_processorMethods.Count)
{
throw new System.Exception("Expected " + numColumnsExpected + " processor methods, only found " + m_processorMethods.Count);
}
for (int i = 0; i < values.Count; ++i)
{
// Pass the value for a column to the processor that handles
// data for that column.
m_processorMethods[i](values[i]);
}
}
DateTime[] s=Convert.ToDateTime(ListA.toArray());
double[] o=ListB.toArray();
object[,] PriceByDate=new object[,]{{s},{o}} ;
}
Warning:
Storing your data in a series of 2D arrays that are supposed to all map to one another by indices, is very fragile. Even storing it in a 2D object array isn't very useful because you'll need to cast those objects to make any use of them, and you'd need to know what data type each column was in order to cast them anyway.
I would highly recommend creating a class that holds the data for a row. Within that class you can store the date, price, and whatever other data you need. Then you can just have a List or array of those objects, each object representing a row.
public class RowObject
{
public DateTime date;
public string price;
}
List<RowObject> m_rowData;
// A delegate that can take the RowObject
private void ProcessorDelegate(string value, RowObject currentRow);
// Pass in the RowObject to your processors
// The processor updates the RowObject with the processed information.
private void DateProcessor(string value, RowObject currentRow)
{
// Make sure 'value' is a date
DateTime date;
if (!DateTime.TryParse(value, out date))
{
// If this field is required you could throw an exception here, or output a console error.
// This is the point at which you could check if 'value' was null or empty.
return;
}
// 'value' was a date, so set this row's date
currentRow.date = date;
}
Now all of your data for a row is tied together nicely, and if there are empty cells then that row's RowObject is missing that data. You could easily validate a row by adding a validation method to RowObject.
public class RowObject
{
public DateTime date;
public string price;
public bool IsValid()
{
if (date == null)
{
// Maybe output a warning to console here
return false;
}
if (string.IsNullOrEmpty(price))
{
// Maybe output a warning to console here
return false;
}
return true;
}
}
Finally
Let me reiterate that a lot of this in reinventing the wheel, if you use the CSVHelper library I provided a link to then you don't need most of this code.
I receive this error when trying to check if an instance of a given XML tag has appeared or not in a previous XML file being read, and therefore, whether or not it should get its own column in the datatable Im creating. To make things short, I create a placeholder array of string which will store column names and I want to check if XMLReader has read a tag with the same name:
// initializing dummy columns
string[] columns;
// check if it is a first time occurance of this tag
for(int n = 0; n < totalcolumns; n++)
{
if (reader.Name == columns[n])
{
columnposition = n;
break;
}
else if(totalcolumns == columntracker+1)
{
// just adding it to the record-keeping array of tables
columns[n] = reader.Name;
column.ColumnName = "reader.Name";
dt.Columns.Add(column);
columnposition = n;
}
columntracker++;
}
I should note that the for loop happens within a switch statement which is simply checking the XML node type. Also, I tried doing a switch but it doesnt allow having a case that is variable, ie using columns[n] in the case declaration.
If you want to initialize columns to an array of totalcolumns strings, it looks like this:
string[] columns = new string[totalcolumns];
While the answer from minitech solves the problem of uninitialized variable, I would use a List instead of the string array. The code gets simpler using List.FindIndex instead of iterating through the string array.
List<String> columns = new List<string>();
columnposition = columns.FindIndex (s => string.Equals(s, reader.Name);
if (columnposition < 0)
{
columns.Add ( reader.Name);
columnposition = columns .Count -1;
// .. do the other stuff
}
I want to change a Datarow value. I want to change DT2[0].ItemArray[3] I tried this code, but it didn't work.
private void Func(DataRow[] DtRowTemp)
{
DataRow[] DT2 ;
DT2 = DtRowTemp; // It work and DTRowTemp value has set to DT2
// above code don't work
DT2[0].ItemArray[3] = 3; // Last Value Was 2 and I want to change this value to 3
}
With the ItemArray Property you get or set the ENTIRE array, not a single value.
Use this to set the fourth item:
DT2[0][3] = 3;