Ms word automation table row problem using C# - c#

What is the main reason of this error
No individual rows in this collection can be addressed because the
table contains vertically linked cells. '
I have table in which I want to add data (stored in array) but second row of the table is merged and I got this error message.
What can be appropriate solution to remove this error.
Code :
if (pCell.Range.Text.Contains("List of components robots"))
{
iDT16 = 0; rowcount = 0;
foreach (int Row in wordDocument.Tables[j].ToString()) // Add row in the table according to data available inside Result array
{
while (sDesignation_Componentsrobots[iDT16] != null)
{
wordDocument.Tables[j].Rows.Add();
wordDocument.Tables[j].Rows.SetHeight(28, Word.WdRowHeightRule.wdRowHeightAtLeast);
rowcount = wordDocument.Tables[j].Rows.Count;
iDT16++;
}
}
// Fill the row with data and add checkbox in particular table column.
iDT16 = 0; iEST16 = 0;
for (int row = 3; row <= rowcount; row++)
{
wTable.AllowAutoFit = true;
wTable.Rows[row].Range.Font.Bold = 0;
wTable.Rows[row].Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
wTable.Rows[row].Range.ParagraphFormat.SpaceBefore = 5;
wTable.Cell(row, 1).Range.Text = sDesignation_Componentsrobots[iDT16];
wTable.Cell(row, 3).Range.Text = sEmergency_stopcircuit_T16[iEST16];
Word.FormField checkBox2 = wTable.Cell(row, 6).Range.FormFields.Add(wTable.Cell(row, 6).Range, Word.WdFieldType.wdFieldFormCheckBox);
iDT16++; iEST16++;
}
break;
}
Table with merged row:

Related

Use Last Value in Column for Summary Instead of SUM

I have following code to remove the summary row/band value on a retrieve UI event. I'm certain this is the wrong way to do it but it works.
public UiEventResult AfterRetrieveData_111(object sender, RetrieveDataEventArgs e)
{
UltraGridBase baseGrid = _view.ViewGrids["ultraGrid1"];
UltraGridLayout gridLayout = baseGrid.DisplayLayout;
for (int i = 0; i < 2; i++)
{
gridLayout.Bands[i].Columns["columnA"].Formula = "''";
}
for (int i = 0; i < 3; i++)
{
gridLayout.Bands[i].Columns["columnB"].Formula = "''";
gridLayout.Bands[i].Columns["columnC"].Formula = "''";
}
Is there a way to program the retrieve so that it populates the summary row for column A/band[2] so that is uses the last value in each column? Without the above code it will sum rows under but would like for a way for it to use the last row value instead. Data will always be sorted DESC by date so last row will always be the value needed...
One way to achieve this is in InitializeRowEvent by setting the value of the columnA to the value of the last row in the child band like this:
// Update the rows only in the first band. You can also use e.Row.Band.Key == {YOU_BAND_KEY}
if (e.Row.Band.Index == 0)
{
// set the value of the columnA to the value of the last row in the child band
e.Row.Cells["columnA"].Value = e.Row.ChildBands.LastRow.Cells["columnA"].Value;
}
Note, this will not work if you edit the cells values. If you need to update the parent row value after cell update, again in InitializeRowEvent you can add this:
// look for row in the secon band
if (e.Row.Band.Index == 1)
{
// get the last row in the second band
if (e.Row == e.Row.ParentRow.ChildBands.LastRow)
{
// get the value of the last row in the second band and set it to the parent row
e.Row.ParentRow.Cells["columnA"].Value = e.Row.Cells["columnA"].Value;
}
}
This will loop through ChildBands and set parent row value with the last value in each ChildBand.
int rowCount = gridLayout.Rows.Count;
for (int i = 0; i < rowCount; i++)
{
foreach (UltraGridChildBand childBand in baseGrid.Rows[i].ChildBands)
{
foreach (UltraGridRow row in childBand.Rows)
{
row.Cells["columnA"].Value =row.ChildBands.LastRow.Cells["columnA"].Value;
}
}
}

Merge values on multi-line columns on a Datagridview

I have Visual Studio 2019. The project is a .Net Windows Form on C# on .Net Framework 4.8.
I have a Datagridview which shows some tables data from different databases (MS SQL and Postgresql).
I merge that databases, and the result is too long, so we can't fit it on a screen; but we have to see all the data available on that screen, which are more than 40 columns. Reduce the font size is not plausible.
So, the solution proposed was to merge some values on the same column in this way (See this example):
The actual data view:
The way we need to view it:
If you have any ideas or you know an alternative to Datagridview which allows that, please share them.
Thanks in advance.
You can customize your datagridview's row and column to get the Multi-line columns on a datagridview.
I assume that the datatable is the table from database.
using System;
using System.Data;
using System.Linq;
using System.Windows.Forms;
private void Form1_Load(object sender, EventArgs e)
{
DataTable table = new DataTable();
table.Columns.Add("Name");
table.Columns.Add("Field1");
table.Columns.Add("Field2");
table.Columns.Add("Field3");
table.Columns.Add("Field4");
table.Columns.Add("Field5");
table.Columns.Add("Field6");
table.Columns.Add("Field7");
table.Rows.Add("test1", 1, 2, 3, 4, 5, 6, 7);
table.Rows.Add("test2", 1, 2, 3, 4, 5, 6, 7);
table.Rows.Add("test3", 1, 2, 3, 4, 5, 6, 7);
dataGridView1.ColumnHeadersVisible = false;
for (int i = 0; i < table.Columns.Count/2; i++)
{
dataGridView1.Columns.Add("","");
}
string[] columnNames = table.Columns.Cast<DataColumn>()
.Select(x => x.ColumnName)
.ToArray();
int count = table.Columns.Count/2;
var col1 = columnNames.Take(count).ToArray();
var col2= columnNames.Skip(count).Take(count).ToArray();
dataGridView1.Rows.Add(col1);
dataGridView1.Rows.Add(col2);
object[] arr;
for (int i = 0; i < table.Rows.Count; i++)
{
arr = table.Rows[i].ItemArray;
var row1=arr.Take(count).ToArray();
var row2 = arr.Skip(count).Take(count).ToArray();
dataGridView1.Rows.Add(row1);
dataGridView1.Rows.Add(row2);
}
}
Result:
After reviewing your question, I have to say that IMHO your solution is not giving much thought to the end user or if the code has to grab one of the values. Stacking fields into a single column “creates” two issues IMHO…One, as mentioned, is that the user is going to have to do extra work and check the order of the headers to distinguish which field value is which… a subtle yet (annoying) non intuitive extra step. Two, if the user is allowed to change fields or the code needs to grad a field, then, there is going to be extra work needed to differentiated which field goes with which value. Extra work for the user and extra work for the coder doesn’t sound like a good start.
Sorry about my rant. Fortunately, if you wanted to take a table as shown in the question, and turn it into a table as you describe, then the code below should do this. It basically creates “two” (2) field columns. Such that each column contains two fields. The code is hacky yet it is not too complicated I hope. I made numerous comments in the code. Some notes would be that, since we are adding two fields for each column and (as far as I know) a DataGridView won’t allow double column headers, the code does NOT use the column headers row and instead uses the first two rows of the grid for the two column headers. This will allow you to format the two rows to look like headers and/or color code if needed.
Lastly, a better solution IMHO. As previously mentioned a pivot will work, however, there are a couple of issues given how the data is stored in the original table. In a basic pivot where we switch rows and columns, the posted example would have three (3) columns… “Jim”, “Hugh” and “Terrance”. Then the number of rows would be (one (1) + however many fields/field columns). The extra “one” is the field “LastName.” Given this, it may look something like…
Jim Hugh Terrance
LastName Carey Jackman Hill
Field1 1 a N/A
Field2 2 b N/A
……..
It would appear obvious that the “LastName” should go with the column header. Therefore, the transpose/pivot may look like…
Jim Carey Hugh Jackman Terrance Hill
Field1 1 a N/A
Field2 2 b N/A
……..
IMHO, this will be more intuitive for the user to identify fields and there should not be any extra coding if we need to reference a specific value. The picture below shows a complete example from the code below. Drop three (3) DataGridViews onto a form and paste my code. The top-left grid is the original data. The bottom-left grid is the transpose as per your requirements and finally, the grid on the right is the what I feel would work best considering your dilemma.
A note on the last grid on the right… Initially, like the column header rows in your solution, I had the fields as a column in the grid. It will not be difficult to change the code if you want this. However, the code currently adds the field names as “row headers” in the grid. Since the DataTable does not really have row headers, this addition had to be made “after” the data source was set and can be seen in the forms Load event. Again, it will not be difficult to move the field to an added column in the DataTable.
To make this example complete, below is code to create some test data. The incoming parameter totalCols will make totalCols columns in the DataTable for the "Field" values.
private DataTable GetDataFromDB(int totalCols) {
DataTable dt = new DataTable();
dt.Columns.Add("Name", typeof(string));
dt.Columns.Add("LastName", typeof(string));
for (int i = 1; i <= totalCols; i++) {
dt.Columns.Add("Field" + i, typeof(string));
}
DataRow curRow;
string name;
string lName;
for (int i = 1; i < 4; i++) {
switch (i) {
case 1:
name = "Jim";
lName = "Carrey";
break;
case 2:
name = "Hugh";
lName = "Jackman";
break;
default:
name = "Terence";
lName = "Hill";
break;
}
curRow = dt.NewRow();
curRow["Name"] = name;
curRow["LastName"] = lName;
if (i < 3) {
for (int j = 2; j < dt.Columns.Count; j++) {
curRow[j] = "N" + i + "F" + (j - 1);
}
}
dt.Rows.Add(curRow);
}
return dt;
}
We will use three (3) global DataTables, one for each grid.
DataTable originalDT;
DataTable pivotDT1;
DataTable pivotDT2;
The load event that sets each grid to the proper DataTable and some specific formatting for each grid.
private void Form1_Load(object sender, EventArgs e) {
originalDT = GetDataFromDB(45);
dataGridView1.DataSource = originalDT;
// pivot 1 - bottom left grid
pivotDT1 = PivotTable(originalDT);
dataGridView2.DataSource = pivotDT1;
dataGridView2.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
dataGridView2.Rows[0].DefaultCellStyle.BackColor = Color.Blue;
dataGridView2.Rows[1].DefaultCellStyle.BackColor = Color.Blue;
dataGridView2.Rows[0].DefaultCellStyle.ForeColor = Color.White;
dataGridView2.Rows[1].DefaultCellStyle.ForeColor = Color.White;
dataGridView2.Columns[0].Frozen = true;
// pivot 2 - right grid
pivotDT2 = PivotTable2(originalDT);
dataGridView3.DataSource = pivotDT2;
int dgvRow = 0;
// add column headers as row headers in the grid
for (int i = 2; i < originalDT.Columns.Count; i++) {
dataGridView3.Rows[dgvRow++].HeaderCell.Value = originalDT.Columns[i].ColumnName;
}
dataGridView3.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders;
}
Finally, the two pivot/transform methods…
Using your solution and shown in the bottom-left grid…
private DataTable PivotTable(DataTable originalDT) {
DataTable pivotDT = new DataTable();
// the number of columns will be half the original number of columns
int halfCols = Math.DivRem(originalDT.Columns.Count, 2, out int rem);
// if there is a remainder then there is an odd number of columns and we need to add 1 col
if (rem > 0) {
halfCols++;
}
// add the columns to the pivot table
for (int i = 0; i < halfCols; i++) {
pivotDT.Columns.Add();
}
// the number of rows will be the number of original rows times 2
// PLUS 2 additional rows for the headers
for (int i = 0; i < (originalDT.Rows.Count * 2) + 2; i++) {
pivotDT.Rows.Add();
}
// Add the two header rows from the column names
int originalCol = 0;
for (int i = 0; i < halfCols; i++) {
pivotDT.Rows[0][i] = originalDT.Columns[originalCol++].ColumnName;
// if the original table had an odd number of columns
// then the last column only had one field
// - there would never be a column without at least one field
if (originalCol < originalDT.Columns.Count) {
pivotDT.Rows[1][i] = originalDT.Columns[originalCol++].ColumnName;
}
}
// finally add the rows from the original table.
int pivotRow = 2;
int pivotCol = 0;
int curPivotRow;
int curPivotCol;
string value;
for (int originalRow = 0; originalRow < originalDT.Rows.Count; originalRow++) {
curPivotRow = pivotRow;
curPivotCol = pivotCol;
for (originalCol = 0; originalCol < originalDT.Columns.Count; originalCol++) {
value = originalDT.Rows[originalRow][originalCol].ToString();
if (string.IsNullOrEmpty(value)) {
value = "N/A";
}
pivotDT.Rows[curPivotRow][curPivotCol] = value;
// if this is the first item then simply bump the pivot row
if (curPivotRow < pivotRow + 1) {
curPivotRow++;
}
else { // this is the second item -
// we want the curpivot row to start back at the starting pivotRow
// then move over a column for the next two columns in the original table
curPivotRow = pivotRow;
curPivotCol++;
}
}
// new row in the original data start back at column 0 in the pivot table
// and bump the row index by two since we added two rows
pivotRow += 2;
pivotCol = 0;
}
return pivotDT;
}
And my solution shown in the grid on the right.
private DataTable PivotTable2(DataTable originalDT) {
DataTable pivotDT = new DataTable();
for (int i = 0; i < originalDT.Rows.Count; i++) {
pivotDT.Columns.Add();
}
for (int i = 0; i < originalDT.Columns.Count - 2; i++) {
pivotDT.Rows.Add();
}
int pivotCol = 0;
foreach (DataRow row in originalDT.Rows) {
pivotDT.Columns[pivotCol++].ColumnName = row[0].ToString() + " " + row[1].ToString();
}
int pivotRow = 0;
pivotCol = 0;
string value;
for (int i = 0; i < originalDT.Rows.Count; i++) {
for (int j = 2; j < originalDT.Columns.Count; j++) {
value = originalDT.Rows[i][j].ToString();
if (string.IsNullOrEmpty(value)) {
value = "N/A";
}
pivotDT.Rows[pivotRow++][pivotCol] = value;
}
pivotCol++;
pivotRow = 0;
}
return pivotDT;
}
Finally, I am not that proficient using SQL, however, I am betting it is possible to create an SQL procedure that will produce my solution directly from the data base.

Loop through selected rows C# DevExpress

I have a function that sets column values for all rows:
The code that sets this:
//Update the engineers for all rows
Btn_ValidateClick_ItemClick(object sender,ItemClickEventArgs e)
{
UpdateTotalTime(gridView);
}
private void UpdateEngineers(DevExpress.XtraGrid.Views.Base.ColumnView View)
{
//Column name that need to be updated (set)
DevExpress.XtraGrid.Columns.GridColumn col = View.Columns.ColumnByFieldName("Engineers");
try
{
int dataRowCount = View.DataRowCount;
for (int i = 0; i < dataRowCount; i++)
{
GridView detail = (GridView)gridView.GetDetailView(i, 0);
string language = gridView.GetRowCellValue(i, "Language").ToString();
for (int y = 0; y < gridView.GetDetailView(i, 0).RowCount; y++)
{
//Add all values found in a detail column to an arraylist
values.Add(detail.GetRowCellValue(y, "EngineerInitials").ToString());
}
if (values.Count >0 )
object t = //string join ...
View.SetRowCellValue(i, col, t);
}
else
{
object t = "No engineers"
View.SetRowCellValue(i, col, t);
}
}
}
}
}
The problem is that now, I want it only to set it for the rows that are selected.
I tried using the .GetSelectedRows()-function and adding the rows to an ArrayList, but this doesn't allow customability really:
private void UpdateTotalTime(DevExpress.XtraGrid.Views.Base.ColumnView View)
{
ArrayList selectedRows = new ArrayList();
for (int i = 0; i < gridView.SelectedRowsCount; i++)
{
if (gridView.GetSelectedRows()[i] >= 0)
selectedRows.Add(gridView.GetDataRow(gridView.GetSelectedRows()[i]));
}
try
{
int count = View.GetSelectedRows().Count();
for (int i = 0; i < selectedRows.Count; i++)
{
//This gets the first row of the count, not the first selected row
GridView detail = (GridView)gridView.GetDetailView(i,0);
}
}
If I select the 3 bottom rows, the first 3 get updated. Why is this?
You are adding all the selected rows to your selectedRows ArrayList. But after that, you are not using it for anything.
I guess what you want (I've never used devexpress controls) is using those selectedrows RowHandle to pass it to the GetDetailView method. According to the GetSelectedRows documentation, the method returns the int handles of the selected rows, so your code should look like this:
First, you must save the DataRow handles, not the DataRow itself, so you must change in your code this line:
selectedRows.Add(gridView.GetDataRow(gridView.GetSelectedRow‌​s()[i]));
into this:
selectedRows.Add(gridView.GetSelectedRows()[i]);
And then, change your loop into this:
for (int i = 0; i < selectedRows.Count; i++)
{
int rowHandle = (int)selectedRows[i];
GridView detail = (GridView)gridView.GetDetailView(rowHandle,0);
}
In fact, you could do everything in just one loop:
private void UpdateTotalTime(DevExpress.XtraGrid.Views.Base.ColumnView View)
{
for (int i = 0; i < gridView.SelectedRowsCount; i++)
{
int rowHandle = gridView.GetSelectedRows()[i];
GridView detail = (GridView)gridView.GetDetailView(rowHandle,0);
}
}

How to read data fast from an excel and convert it to list from file stream

I am using EPPlus.
The excel I am uploading has column headers in row number 2 . And from row 4 onward it has the data which may vary up to 2k records.
The way I am doing it , it takes a lot of time for reading 2k records and putting to a list .
using (var excel = new ExcelPackage(hpf.InputStream))
{
var ws = excel.Workbook.Worksheets["Sheet1"];
//Read the file into memory
for (int rw = 4; rw <= ws.Dimension.End.Row; rw++)
{
if (!ws.Cells[rw, 1, rw, 24].All(c => c.Value == null))
{
int headerRow = 2;
GroupMembershipUploadInput gm = new GroupMembershipUploadInput();
for (int col = ws.Dimension.Start.Column; col <= ws.Dimension.End.Column; col++)
{
var s = ws.Cells[rw, col].Value;
if (ws.Cells[headerRow, col].Value.ToString().Equals("Existing Constituent Master Id"))
{
gm.cnst_mstr_id = (ws.Cells[rw, col].Value ?? (Object)"").ToString();
}
else if (ws.Cells[headerRow, col].Value.ToString().Equals("Prefix of the constituent(Mr, Mrs etc)"))
{
gm.cnst_prefix_nm = (ws.Cells[rw, col].Value ?? (Object)"").ToString();
}
}
lgl.GroupMembershipUploadInputList.Add(gm);
}
}
GroupMembershipUploadInputList is the list of objects of type GroupMembershipUploadInput that I am adding the excel values to after reading from the cell wise.
Can it be done faster ? What am I missing here ?
Please help to improve the performance.
You are making a lot iterations there, for each row, you visit each column twice. I assume that you only need those two values per row and if so the following code would reduce time drastically:
using (var excel = new ExcelPackage(hpf.InputStream))
{
var ws = excel.Workbook.Worksheets["Sheet1"];
int headerRow = 2;
// hold the colum index based on the value in the header
int col_cnst_mstr_id = 2;
int col_cnst_prefix_nm = 4;
// loop once over the columns to fetch the column index
for (int col = ws.Dimension.Start.Column; col <= ws.Dimension.End.Column; col++)
{
if ("Existing Constituent Master Id".Equals(ws.Cells[headerRow, col].Value))
{
col_cnst_mstr_id = col;
}
if ("Prefix of the constituent(Mr, Mrs etc)".Equals(ws.Cells[headerRow, col].Value))
{
col_cnst_prefix_nm = col;
}
}
//Read the file into memory
// loop over all rows
for (int rw = 4; rw <= ws.Dimension.End.Row; rw++)
{
// check if both values are not null
if (ws.Cells[rw, col_cnst_mstr_id].Value != null &&
ws.Cells[rw, col_cnst_prefix_nm].Value != null)
{
// the correct cell will be selcted based on the column index
var gm = new GroupMembershipUploadInput
{
cnst_mstr_id = (string) ws.Cells[rw, col_cnst_mstr_id].Value ?? String.Empty,
cnst_prefix_nm = (string) ws.Cells[rw, col_cnst_prefix_nm].Value ?? String.Empty
};
lgl.GroupMembershipUploadInputList.Add(gm);
}
}
}
I removed the inner column loop and moved it to the start of the method. There it is used to just get the columnindex for each field you're interested in. The expensive null check can now also be reduced. To fetch the value, all that is now needed is a simple index lookup in the row.

Insert Data to Database - LINQ

There is some data in my datagridview. I want to insert all that data at once into database from datagridview using LINQ. But, it couldn't be inserted.
Here is my code :
DetailTransaction dt = new DetailTransaction();
for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
dt.TransactionID = labelID.Text;
dt.ProductID = dataGridView1.Rows[i].Cells[0].Value.ToString();
dt.Quantity = int.Parse(dataGridView1.Rows[i].Cells[4].Value.ToString());
}
dc.DetailTransactions.InsertOnSubmit(dt);
dc.SubmitChanges();
Can anyone tell me what is the correct code?
You need to populate a collection of DetailTransaction and then use InsertAllOnSubmit like:
List<DetailTransaction > list = new List<DetailTransaction>();
for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
DetailTransaction dt = new DetailTransaction();
dt.TransactionID = labelID.Text;
dt.ProductID = dataGridView1.Rows[i].Cells[0].Value.ToString();
dt.Quantity = int.Parse(dataGridView1.Rows[i].Cells[4].Value.ToString());
list.Add(dt);
}
dc.DetailTransactions.InsertAllOnSubmit(list);
dc.SubmitChanges();
With your current code, you will end up with a single row inserted in the database and that row will be holding the records of the last row in dataGridView1

Categories

Resources