C# Add rows/columns to existing DataTable in separate method - c#

EDIT: Sorry guys, I was a little blind with this thing, I posted the "solution" below under the "problem".
ORIGINAL:
I can't seem to get behind this problem (even after searching a lot on SO):
I have a DataTable object that I want to "prepare" for data population like so:
My model class property (classname "DataMdl"):
private static DataTable _datatbl;
public DataTable datatbl
{
get { return _datatbl; }
set
{
_datatbl = value;
OnPropertyChanged(new PropertyChangedEventArgs("datatbl"));
}
}
Call of prepdatatable in a ViewModel-Method:
prepdatatable(DataMdl.datatbl, 5, 9)
My prepdatatable method:
private void prepdatatable(DataTable dt, int rowcount, int colcount)
{
dt = new DataTable();
int i = 0;
for (i = 0; i < colcount; i++)
{
dt.Columns.Add("col" + i);
}
for (i = 0; i < rowcount; i++)
{
dt.Rows.Add("row" + i);
}
}
After calling the above method I try to populate the DataTable in the method which called prepdatatable with
DataMdl.datatbl.Rows[0][0] = "..."
DataMdl.datatbl.Rows[0][1] = "..."
...
DataMdl.datatbl.Rows[1][0] = "..."
...
and so on
In this case I get a NullReferenceException on DataMdl.datatbl.
When I try to initialize DataMdl.datatbl outside of prepdatatable, I get a NullReferenceException inside on "dt" inside prepdatatable.
"SOLUTION":
Just make prepdatatable return a DataTable instead of void and then assign its value to the property DataMdl.datatbl:
private DataTable prepdatatable(int rowcount, int colcount)
{
var dt = new DataTable();
int i = 0;
for (i = 0; i < colcount; i++)
{
dt.Columns.Add("col" + i);
}
for (i = 0; i < rowcount; i++)
{
dt.Rows.Add("row" + i);
}
return dt;
}
and
DataMdl.datatbl = prepdatatable(5, 9);
Thanks for your concern.

What are you trying to do here?
for (i = 0; i < rowcount; i++)
{
var r = dt.NewRow();
dt.Rows.Add(r);
}
I have a feeling that this is the source of the exception. Take a look at this: How to add a new row to c# DataTable in 1 line of code? . It seems like you are trying to add a row that doesn't have a value. A new row is, by default, null, but initialized. Then, you try to add it to the datatable. Why don't you add rows like you are adding columns?

suppose that this is the initialization of your datatable and here you are calling the method
datatbl = new DataTable();
prepdatatable(datatbl, 5, 9);
this method will not take the datatable , it will take a copy from it , you are sending the datatable by value not by reference
private void prepdatatable(DataTable dt, int rowcount, int colcount)
{
int i = 0;
for (i = 0; i < colcount; i++)
{
dt.Columns.Add("col" + i);
}
for (i = 0; i < rowcount; i++)
{
var r = dt.NewRow();
dt.Rows.Add(r);
}
}
I will suppose that your method should work fine and the problem is only in the parameter sending way
just change
the method call to
prepdatatable(ref datatbl, 5, 9);
the header to
private void prepdatatable(ref DataTable dt, int rowcount, int colcount)

Related

Is there a graceful way to display a big 2 dimensional array in WPF like excel?

I am working with a requirement for displaying a 2 dimensional array in a WPF window. The size of array can be up to 360*720. I have tried to use DataTable bound to a DataDrid, but it took so much time to load the grid and very RAM consuming. My example code is below.
public void SetData(double[][] array)
{
if(array.Length <= 0)
return;
DataTable table = new DataTable();
for (int i = 0; i < array[0].Length; i++)
{
table.Columns.Add(i.ToString(), typeof(double));
}
for (int i = 0; i < array.Length; i++)
{
DataRow row = table.NewRow();
for (int j = 0; j < array[i].Length; j++)
{
row[j] = array[i][j].ToString();
}
table.Rows.Add(row);
}
dataGrid.DataContext = table;
}
I created an array of double of which the dimension is 360 * 720 and called the SetData() method above. As a result, the RAM occupied by the program increased several GBs and very time consuming.
I wonder if there is a graceful way to solve this problem or there are some shortcomings in my code. Thank you.
Thanks for all these useful answers and comments which helped me a lot. After doing some search, I seemed to find out the reason why my code costs so much time to render the DataGrid. I wrapped up the grid with a ScrollViewer before. I removed it and it went very well. Here is the link where I found the tip.WPF DataGrid is very slow to render.
Thank you all.
Create a separate class that can return the enumerator as data.
class ArrayVisitor : IEnumerable<double[]>
{
private double[,] _data;
public ArrayVisitor()
{
}
public ArrayVisitor(double[,] data)
{
_data = data;
}
public double[,] Data
{
get { return _data; }
set { _data = value; }
}
#region IEnumerable<double[]> Members
public IEnumerator<double[]> GetEnumerator()
{
if (_data == null)
throw new ArgumentException("Data cannot be null.", "Data");
int len2d = _data.GetLength(1);
for (int i = 0; i < _data.GetLength(0); i++)
{
double[] arr = new double[len2d];
for (int j = 0; j < len2d; j++)
{
arr[j] = _data[i, j];
}
yield return arr;
}
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
}
You can use Listview and Gridview as Databound controls.
private void Bindng2DArrayToListview2
(ListView listview, double[,] data, string[] columnNames)
{
Check2DArrayMatchColumnNames(data, columnNames);
GridView gv = new GridView();
for (int i = 0; i < data.GetLength(1); i++)
{
GridViewColumn col = new GridViewColumn();
col.Header = columnNames[i];
col.DisplayMemberBinding = new Binding("[" + i + "]");
gv.Columns.Add(col);
}
ArrayVisitor arrayVisitor = new ArrayVisitor(data);
listview.View = gv;
listview.ItemsSource = arrayVisitor;
}

How to convert nested for loops with if condition to LINQ

I have a horrible method that extracts data from a DataTable and converts it to a desirable formatted DataTable. I'm sure there is a much nicer way to do this in LinQ but I'm not really experienced with it. I would appreciate if somebody could show me a nicer solution.
Heres the code
private static void ExtractImportLayoutFromExcelDt(DataTable importDt, DataTable dtExtracted, int languages)
{
// The number of Locale colums included in the excel file.
for (int x = 0; x < languages; x++)
{
// The total number of friendlynames-keys / language included in the excel.
for (int j = 0; j < dtExtracted.Rows.Count; j++)
{
var row = dtExtracted.Rows[j];
DataRow tempRow = importDt.NewRow();
// Filling in the 3 columns. (FriendlyName - LocaleID - Text)
for (int i = 0; i <= 2; i++)
{
if (i == 0)
{
tempRow[i] = row[i]; // Friendly names: This is always going to be column 1 [0].
}
else if (i == 1)
{
tempRow[i] = Regex.Match(dtExtracted.Columns[x + 1].ToString(), #"\d+").Value; // LocaleIDs: Getting rid of non numeric characters from this column.
}
else
{
tempRow[i] = row[x + 1];
}
}
importDt.Rows.Add(tempRow);
}
}
}
i would rewrite inner for loops
for (int x = 0; x < languages; x++)
foreach (DataRow row in dtExtracted.Rows)
importDt.Rows.Add
(
row[0],
Regex.Match(dtExtracted.Columns[x + 1].ToString(), #"\d+").Value; // LocaleIDs: Getting rid of non numeric characters from this column.
row[x + 1]
);
DataTable.Rows collection has overload of Add method, which accept an array of objects: Add
I am not quite sure if LINQ could be of any help here, but apart from rewriting the entire mapping logic I would at least split this method into two:
private static void ExtractImportLayoutFromExcelDt(DataTable importDt, DataTable dtExtracted, int languages)
{
// The number of Locale colums included in the excel file.
for (int x = 0; x < languages; x++)
{
// The total number of friendlynames-keys / language included in the excel.
for (int j = 0; j < dtExtracted.Rows.Count; j++)
{
AddRow(importDt, dtExtracted, dtExtracted.Rows[j], x+1);
}
}
}
private static void AddRow(DataTable table, DataTable dtExtracted, DataRow originalRow, int language)
{
var row = table.NewRow();
row[0] = originalRow[0];
row[1] = Regex.Match(dtExtracted.Columns[language].ToString(), #"\d+").Value;
row[2] = originalRow[language];
table.Rows.Add(row);
}
You can write something like this but in this case is more like abuse of Linq but if you do it for educational purposes ..The main power of LINQ is when you want to enumerate or filter for example collections not in cases like this.
public static void ExtractImportLayoutFromExcelDt(DataTable importDt, DataTable dtExtracted, int languages)
{
Enumerable.Range(0, languages)
.ToList().ForEach(x =>
{
Enumerable.Range(0, dtExtracted.Rows.Count)
.ToList().ForEach(j =>
{
var row = dtExtracted.Rows[j];
DataRow tempRow = importDt.NewRow();
AddRow(importDt, dtExtracted, x, row, tempRow);
});
});
}
private static void AddRow(DataTable importDt, DataTable dtExtracted, int x, DataRow row, DataRow tempRow)
{
for (int i = 0; i <= 2; i++)
{
if (i == 0)
{
tempRow[i] = row[i]; // Friendly names: This is always going to be column 1 [0].
}
else if (i == 1)
{
tempRow[i] = Regex.Match(dtExtracted.Columns[x + 1].ToString(), #"\d+").Value; // LocaleIDs: Getting rid of non numeric characters from this column.
}
else
{
tempRow[i] = row[x + 1];
}
}
importDt.Rows.Add(tempRow);
}

How do I use For Loop twice to find datatable columns and rows?

I want to use for loop to get average of datatable columns and rows. What I want to do is that what if there are 100 ~ 1000 columns and rows, I can't keep on adding them in the code. is there one simple code that can get average of automatically as I add columns and rows?
here is my code, I am stuck I don't know what to write in ?? area below and this code gets me error please help...
private void button1_Click(object sender, EventArgs e)
{
DataTable dtGrid = gridData.DataSource as DataTable;
DataTable dtResult = new DataTable();
Math columnIndex = new Math();
List<double> avgList = new List<double>();
for (int i = 0; i < dtGrid.Columns.Count; i++)
{
for (int k = 1; k < dtGrid.Rows.Count; k++)
{
// ??
avgList.Add(Convert.ToDouble(dtGrid.Rows[i].ToString()));
}
}
//this is from other class name Math
/* public double getAverageValue(List<double> avgList)
{
double averageList = 0;
averageList = MathNet.Numerics.Statistics.Statistics.Mean(avgList.ToList());
return averageList;
}*/
double averageX1 = columnIndex.getAverageValue(avgList);
List<Math> list = new List<Math>();
//using get; set from other class
list.Add(new Math { Result = "Average", X1 = averageX1.ToString() });
gridData2.DataSource = list;
}
}
}
It looks like your loop is inside out. Try this:
DataTable dtGrid = gridData.DataSource as DataTable;
DataTable dtResult = new DataTable();
Math columnIndex = new Math();
List<double> avgList = new List<double>();
for (int k = 1; k < dtGrid.Rows.Count; k++)
{
for (int i = 0; i < dtGrid.Columns.Count; i++)
{
// ??
avgList.Add(Convert.ToDouble(dtGrid.Rows[k].Columns[i].ToString()));
}
}
This logic averages all columns in a row together. If you need,, you can create a Dictionary and average each column separately. Something like thisL
Dictionary<int, List<double>> AvgColumnList = new Dictionary<int, System.Collections.Generic.List<double>>();
This uses a dictionary that contains a list for each column in the row. If there are 100 columns, then there will be 100 entries in the dictionary with index 0 - 99. Each dictionary item will contain a list of doubles.
for (int k = 1; k < dtGrid.Rows.Count; k++)
{
for (int i = 0; i < dtGrid.Columns.Count; i++)
{
if (!AvgColumnList.Keys.Contains(i))
AvgColumnList.Add(i, new List<double>());
AvgColumnList[i].Add(Convert.ToDouble(dtGrid.Rows[k].Columns[i].ToString()));
}
}
DataTable is zero index based, in your code row count started from 1 it should be 0, also dtGrid.Rows[i] is a row not the cell value. Use below code to loop through each cell of a DataTable
Update : Code updated as OP want to save each column data separately and irrespective of column numbers.
List<List<double>> perColumnAvg = new List<List<double>>();
for (int i = 0; i < dtGrid.Columns.Count; i++)
{
avgList = new List<double>();
for (int k = 0; k < dtGrid.Rows.Count; k++)
{
// ??
avgList.Add(Convert.ToDouble(dtGrid.Rows[k][i].ToString()));
}
perColumnAvg.Add(avgList);
}
Now you can compute individual column average as
foreach (var columnList in perColumnAvg)
{
// place your logic here.
columnIndex.getAverageValue(columnList);
}
And can compute avg across table using.
double tableAvg = columnIndex.getAverageValue(perColumnAvg.SelectMany(s=>s));

Is it possible to copy row (with data, merging, style) in Excel using Epplus?

The problem is that I need to insert data into Excel from the collection several times using a single template for the entire collection.
using (var pckg = new ExcelPackage(new FileInfo(association.TemplatePath)))
{
var workSheet = pckg.Workbook.Worksheets[1];
var dataTable = WorksheetToDataTable(workSheet);
/*Some stuff*/
FindAndReplaceValue(workSheet, dictionary, row);
}
private DataTable WorksheetToDataTable(ExcelWorksheet oSheet)
{
int totalRows = oSheet.Dimension.End.Row;
int totalCols = oSheet.Dimension.End.Column;
DataTable dt = new DataTable(oSheet.Name);
DataRow dr = null;
for (int i = 1; i <= totalRows; i++)
{
if (i > 1) dr = dt.Rows.Add();
for (int j = 1; j <= totalCols; j++)
{
if (i == 1)
dt.Columns.Add((oSheet.Cells[i, j].Value ?? "").ToString());
else
dr[j - 1] = (oSheet.Cells[i, j].Value ?? "").ToString();
}
}
return dt;
}
First picture - My template. Second - First element of collection (with data, style, merging). Third - Other elements has only data
I just made copies of rows
for (int i = 0; i < invoiceList.Count; i++)
{
workSheet.Cells[1, 1, totalRows, totalCols].Copy(workSheet.Cells[i * totalRows + 1, 1]);
}
If you want to copy range just use :
workSheet.Cells["A1:I1"].Copy(workSheet.Cells["A4:I4"]);

Is it possible to populate a DataGridView with alternating vertical columns?

I need to display data that will be hybrid "hard-coded" strings and data from a database. Specifically, every even-numbered column contains string values that are NOT from the database, and every odd-numbered column contains data. So column 1, for example, will contain values 1 through 12 from the database, so that the first two columns look something like this (and the same pattern repeats several times):
00:00 BoundVal1
00:15 BoundVal2
. . .
02:45 BoundVal12
Is this possible?
Right now I'm using a TableLayoutPanel for this, but the coding for that is a little pretzly (not a Steely Dan reference).
You could just build a DataTable object and set it as DataGridView's DataSource.
I finally got this working, based on code from Mohamed Mansour at http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/ca89a857-6e17-44a7-8cdb-90d64a0a7bbe
int RowCount = 12;
Dictionary<int, string> PlatypusPairs;
. . .
private void button3_Click(object sender, EventArgs e) {
// Retrieve data as dictionary
PlatypusPairs = InterpSchedData.GetAvailableForPlatypusAndDate(oracleConnectionTestForm, 42, DateTime.Today.Date);
int ColumnCount = 16;
// Add the needed columns
for (int i = 0; i < ColumnCount; i++) {
string colName = string.Format("Column{0}", i + 1);
dataGridView1.Columns.Add(colName, colName);
}
for (int row = 0; row < RowCount; row++) {
// Save each row as an array
string[] currentRowContents = new string[ColumnCount];
// Add each column to the currentColumn
for (int col = 0; col < ColumnCount; col++) {
currentRowContents[col] = GetValForCell(row, col);
}
// Add the row to the DGV
dataGridView1.Rows.Add(currentRowContents);
}
}
private string GetValForCell(int Row, int Col) {
string retVal;
if (Col % 2 == 0) {
retVal = GetTimeStringForCell(Row, Col);
} else {
retVal = GetPlatypusStringForCell(Row, Col);
}
return retVal;
}
private string GetTimeStringForCell(int Row, int Col) {
const int TIME_INCREMENT_STEP = 15;
var dt = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 0, 0, 0);
dt = dt.AddMinutes(((Col * (RowCount / 2)) + Row) * TIME_INCREMENT_STEP);
return dt.ToString("HH:mm");
}
private string GetPlatypusStringForCell(int Row, int Col) {
int multiplicand = Col / 2;
string val = string.Empty;
int ValToSearchFor = (multiplicand * RowCount) + (Row + 1);
if (PlatypusPairs.ContainsKey(ValToSearchFor)) {
PlatypusPairs.TryGetValue(ValToSearchFor, out val);
if (val.Equals(0)) {
val = string.Empty;
}
}
return val;
}

Categories

Resources