I am making an add-on for SAP B1 8.82. I would like automatic row numbering for my matrix column "#" i.e. UID "V_-1". Is there a feature in 8.82 that can help me do this?
I am using UserDataSource for my matrix. How do I get to have row numbers that automatically update when I add/remove a row?
I have the following added to menu event 1292 for adding row numbers manually but cant get it to work for row deletion - menu event 1293. (Using C#)
case "1292"://Add Matrix Row
_form.Freeze(true);
_form.DataSources.UserDataSources.Item("itemNo").Value = "";
_form.DataSources.UserDataSources.Item("itemDesc").Value = "";
_form.DataSources.UserDataSources.Item("quantity").Value = "";
_form.DataSources.UserDataSources.Item("warehouse").Value = "";
_form.DataSources.UserDataSources.Item("distrRule").Value = "";
_form.DataSources.UserDataSources.Item("project").Value = "";
oMatrixItem.AddRow();
// row numbering
int i = 1;
int j = 0;
j = oMatrixItem.RowCount;
while (j >= i)
{
oMatrixItem.Columns.Item("V_-1").Cells.Item(i).Specific.Value = i.ToString();
i = i + 1;
}
_form.Freeze(false);
break;
In order to get the number in "V_-1" column simply
we can bind our matrix to a table and link the line ID column to "V_-1" column
we can use a DBDataSource for binding the matrix to the table and link the "V_-1" to matrix column
However if we need to maintain the serial number even after deleting the row we need to write separate code after loading the data into matrix in form load after action event.
Sample pseudo-code:
for i as integer = 1 to matrix.rowcount
matrix.columns.itm(V_-1).item().cells(i)=i
next
this is what I did after adding row to matrix.
Column name was not working for me, but index '0' was ok.
iRow is index of row where I want to update this index in # column.
SAPbouiCOM.EditText oEditID = (SAPbouiCOM.EditText) oMatrix.Columns.Item(0).Cells.Item(iRow).Specific;
oEditID.Active = true;
oEditID.Value = iRow.ToString();
Related
So I have this code:
using Excel = Microsoft.Office.Interop.Excel;
...
public static void AppendColumnValuesGivenStart(Excel.Worksheet wks, int column, int columnStarts, int totalColumnsInTable, List<ColumnValues> columnValues)
{
for (int x = 0; x < columnValues.Count; x++)
{
for (int y = 0; y < totalColumnsInTable; y++)
{
wks.Cells[x + columnStarts, column + y].Insert(Excel.XlInsertShiftDirection.xlShiftDown, Type.Missing);
}
}
}
It is supposed to insert a new row at the specified position of cell, but in the output it makes a new row that spans all the way to the right and interferes with other tables.
What am I doing wrong and what would be the correct way of inserting a new row in the specified cell?
If I've understood you correctly, you want to insert rows into a very selective set of cells in your worksheet.
I have this worksheet with a matrix of data ...
I then want to insert 5 rows between columns E and J to give me a result that looks like this ...
To achieve this, there are a few ways to do it.
You can do it via selecting the range specifically and running the insert method ...
xlWorksheet.Range["E6:J10"].Insert(XlInsertShiftDirection.xlShiftDown);
You can obviously pass in parameters to make it happen as well ...
int rowFrom = 6;
int rowTo = 10;
xlWorksheet.Range[$"E{rowFrom}:J{rowTo}"].Insert(XlInsertShiftDirection.xlShiftDown);
Or you can do it using cell references ...
var cellAddressFrom = ((Range)xlWorksheet.Cells[6, 5]).Address;
var cellAddressTo = ((Range)xlWorksheet.Cells[10, 10]).Address;
xlWorksheet.Range[$"{cellAddressFrom}:{cellAddressTo}"].Insert(XlInsertShiftDirection.xlShiftDown);
Or more again, cell references using column letters, not numbers ...
var cellAddressFrom = ((Range)xlWorksheet.Cells[6, "E"]).Address;
var cellAddressTo = ((Range)xlWorksheet.Cells[10, "J"]).Address;
xlWorksheet.Range[$"{cellAddressFrom}:{cellAddressTo}"].Insert(XlInsertShiftDirection.xlShiftDown);
Bottom line, you need to get your parameters right and you need to make up a matrix (unless it's a single cell you want to shift down) in order to shift cells down or right.
I have a datagridview in a winform that I read data into. I name each column after the count in the loop I use. Part of the function reading the data is below. The file I read from is a csv created from excel.
while (!parser.EndOfData)
{
string[] fields = parser.ReadFields(); //read in the next row of data
dgv_data.Rows.Add(); // add new row
rowCount++;
//put row number inside left margin
dgv_data.Rows[rowCount - 1].HeaderCell.Value = rowCount.ToString();
for (int i = 0; i < col; i++)
{
dgv_data.Rows[rowCount - 1].Cells[i].Value = fields[i]; //put the data into the cell
//If the cell is true or a number greater than 1 then we colour it green
if (fields[i].ToLower() == "true") dgv_data.Rows[rowCount - 1].Cells[i].Style.BackColor = Color.SpringGreen;
if (int.TryParse(fields[i], out num))
{
if (int.Parse(fields[i]) > 0) dgv_data.Rows[rowCount - 1].Cells[i].Style.BackColor = Color.SpringGreen;
}
dgv_data.Rows[rowCount - 1].Cells[i].Tag = (rowCount - 1).ToString() + ":" + i.ToString(); //Unique cell tag
}
}
I need to reorder the columns as I need to save in a different order BUT I also need to reorder them back to original order so flip-flop between the two different orders. This I do with a simple function, I only show a few of the columns here as there are 30 in total. This works well even if a bit inefficient.
private void btn_reorder_Click(object sender, EventArgs e)
{
if (flag)
{
flag = false;
dgv_data.Columns[22].DisplayIndex = 0;
dgv_data.Columns[20].DisplayIndex = 1;
dgv_data.Columns[12].DisplayIndex = 2;
}
else
{
flag = true;
dgv_data.Columns[0].DisplayIndex = 0;
dgv_data.Columns[1].DisplayIndex = 1;
dgv_data.Columns[2].DisplayIndex = 2;
}
dgv_data.Refresh();
}
The issue comes when I need to save the data to a csv file, I do not get them saved in the new order. Before I save it I need to manipulate a few columns e.g change seconds to milliseconds. Using the following method, I can do this but when I save the file it always has the original layout.
var sb = new StringBuilder();
foreach (DataGridViewRow row in dgv_data.Rows)
{
row.Cells[1].Value = (int.Parse(row.Cells[1].Value.ToString()) * 1000).ToString();
var cells = row.Cells.Cast<DataGridViewCell>();
sb.AppendLine(string.Join(",", cells.Select(cell => "\"" + cell.Value + "\"").ToArray()));
}
File.WriteAllText(saveFileDialog1.FileName, sb.ToString());
I found on internet a different method and this does save the new layout but I cannot manipulate the cells before I save them.
dgv_data.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText;
// Select all the cells
dgv_data.SelectAll();
// Copy selected cells to DataObject
DataObject dataObject = dgv_data.GetClipboardContent();
// Get the text of the DataObject, and serialize it to a file
File.WriteAllText(saveFileDialog1.FileName,
dataObject.GetText(TextDataFormat.CommaSeparatedValue));
How can I make sure that when I reorder the columns that I can save them in the same order as they are show in the DataGridView and still be able to flip-flop between the two column orders?
DataGridView Column have many ways you can address them, two are their name or their index number in the DataGridView Collection.
The user creates the Column name, but the Index is created by the system when columns are created, and I cannot see a way to ever edit this number.
If you want to reorder, the visual order of your columns in the GUI you change the DisplayIndex. This does not Change the Index number of the column. It just changes how the DGV looks in the UI.
I created a small example which you can download from https://github.com/zizwiz/DataGridView_ReorderColumn_Example
When you save the left hand reordered DGV by copy to clipboard you get the view in the GUI but if you save by parsing through the grid you get the original Index view.
To get round this if you want to reorder and then save that by parsing through the DGV then you must Copy the original DGV to a new DGV column by column in the order you now want. There are many ways to do this I just show on simple way what you would probably want to do is put the column in a temporary list, remove all columns and add them again.
I cannot find a way of changing the Index property of a column after it has been created, so this copy method although cumbersome is what I have used.
As this is only a quick example it does not have all the bells and whistles one might want to use, it just illustrates how I got over a problem I encountered. The files you save are put in the same folder as the "exe" you run.
My Excel file is not in tabular data. I am trying to read from an excel file.
I have sections within my excel file that are tabular.
I need to loop through rows 3 to 20 which are tabular and read the data.
Here is party of my code:
string fileName = "C:\\Folder1\\Prev.xlsx";
var workbook = new XLWorkbook(fileName);
var ws1 = workbook.Worksheet(1);
How do I loop through rows 3 to 20 and read columns 3,4, 6, 7, 8?
Also if a row is empty, how do I determine that so I can skip over it without reading that each column has a value for a given row.
To access a row:
var row = ws1.Row(3);
To check if the row is empty:
bool empty = row.IsEmpty();
To access a cell (column) in a row:
var cell = row.Cell(3);
To get the value from a cell:
object value = cell.Value;
// or
string value = cell.GetValue<string>();
For more information see the documentation.
Here's my jam.
var rows = worksheet.RangeUsed().RowsUsed().Skip(1); // Skip header row
foreach (var row in rows)
{
var rowNumber = row.RowNumber();
// Process the row
}
If you just use .RowsUsed(), your range will contain a huge number of columns. Way more than are actually filled in!
So use .RangeUsed() first to limit the range. This will help you process the file faster!
You can also use .Skip(1) to skip over the column header row (if you have one).
I'm not sure if this solution will solve OP's problem but I prefer using RowsUsed method. It can be used to get the list of only those rows which are non-empty or has been edited by the user. This way I can avoid making emptiness check while processing each row.
Below code snippet can process 3rd to 20th row numbers out of all the non-empty rows. I've filtered the empty rows before starting the foreach loop. Please bear in mind that filtering the non-empty rows before starting to process the rows can affect the total count of rows which will get processed. So you need to be careful while applying any logic which is based on the total number of rows processed inside foreach loop.
string fileName = "C:\\Folder1\\Prev.xlsx";
using (var excelWorkbook = new XLWorkbook(fileName))
{
var nonEmptyDataRows = excelWorkbook.Worksheet(1).RowsUsed();
foreach (var dataRow in nonEmptyDataRows)
{
//for row number check
if(dataRow.RowNumber() >=3 && dataRow.RowNumber() <= 20)
{
//to get column # 3's data
var cell = dataRow.Cell(3).Value;
}
}
}
RowsUsed method is helpful in commonly faced problems which require processing the rows of an excel sheet.
It works easily
XLWorkbook workbook = new XLWorkbook(FilePath);
var rowCount = workbook.Worksheet(1).LastRowUsed().RowNumber();
var columnCount = workbook.Worksheet(1).LastColumnUsed().ColumnNumber();
int column = 1;
int row = 1;
List<string> ll = new List<string>();
while (row <= rowCount)
{
while (column <= columnCount)
{
string title = workbook.Worksheets.Worksheet(1).Cell(row, column).GetString();
ll.Add(title);
column++;
}
row++;
column = 1;
}
I have tried the below C# CODING:
wsDt.Cells["A10:G10"].AutoFilter = false;
but the filter is not removed from my excel.
Any other way to remove it.
Thanks...
In Excel, when you use the Format as Table option it will not only style the data but will also create a Named Range - Table1. This option also automatically enables the Filter Buttons. After formatting as a table, you can uncheck Filter Buttons in the Table Tools -> Table Styles Options.
What works for me is doing the same programmatically.
LoadFromDataTable(DataTable, bool, TableStyles) basically
pastes data to the worksheet starting at the ExcelRange
applies a Table Format
uses the DataTable.TableName to name the range
enables Filter Button
Disable the Filter Button
use the DataTable.TableName to reference the named range
set ShowFilter to false
enter code here
//imagine a table with 5 columns
DataTable dt = new DataTable();
dt.TableName = "UniqueTableName";
//define the cells where the headers will appear
int topRow = 1;
int leftMostColumn = 1;
int rightMostColumn = 5;
//bind the DataTable using LoadFromDataTable()
OfficeOpenXml.ExcelRange excelRange = worksheet.Cells[topRow, leftMostColumn, topRow, rightMostColumn];
excelRange.LoadFromDataTable(dt, true, OfficeOpenXml.Table.TableStyles.Light8);
//turn of the filtering
OfficeOpenXml.Table.ExcelTable table = worksheet.Tables[dt.TableName];
table.ShowFilter = false;
This seems to be an EPPlus bug and I don't think it has been resolved as of the latest release (4.04), at least I could figure out a solution. My workaround is to simply load the spreadsheet values a row at a time with a loop:
int sheetRow = 3;
for (int outer = 0; outer < outerSourceTable.Rows.Count; outer++)
{
var outerThingId = Convert.ToInt32(outerSourceTable.Rows[outer]["OuterThingId"]);
var outerThingName = Convert.ToString(outerSourceTable.Rows[outer]["OuterThing"]);
var innerThingsTable = _repository.GetInnerThings(outerThingId);
if (innerThingsTable.Rows.Count > 0)
{
myWorksheet.Cells[sheetRow, 1].Value = outerThingName;
// Load the data into the worksheet. We need to load a row at a time
// to avoid the auto-filter bug
for (int inner = 0; inner < innerThingsTable.Rows.Count; inner++)
{
var innerName = Convert.ToString(innerThingsTable.Rows[inner]["Name"]);
var innerDescr = Convert.ToString(innerThingsTable.Rows[inner]["Description"]);
myWorksheet.Cells[sheetRow, 2].Value = innerName;
myWorksheet.Cells[sheetRow, 3].Value = innerDescr;
sheetRow++;
}
sheetRow++;
}
}
If you populate your excel data using the LoadFromCollection() call. You can then reference it using the default Excel table name of "Table 1".
This is the same idea as Patricks answer but demonstrates the use without DataTable.
excelWorksheet.Cells.LoadFromCollection(myCollection);
ExcelTable table = excelWorksheet.Tables["Table1"];
table.ShowFilter = false;
Sometimes excel creates the Table as a named range. In my instance the Table was the only thing in the first worksheet, so the following helped me:
var ws = wb.Worksheets.First();
ws.NamedRanges.FirstOrDefault()?.Ranges.FirstOrDefault()?.SetAutoFilter(false);
I have a datagridview with 5(0-5) columns. All the rows value I retrieve from the hashtable created.
Now I've set a condition that state if column 4 contain empty value from hashtable then add new column next to column 4 which makes the new added column index at position 5 and the value of hashtable previously for column 5 change to column 7.
I do the code like this:
int number = dataGridView1.Rows.Add();
dataGridView1.Rows[number].Cells[0].Value = result; //id
dataGridView1.Rows[number].Cells[1].Value = newAddress; //ip
dataGridView1.Rows[number].Cells[2].Value = (string)((Hashtable)ht[1])["value"]; //name
dataGridView1.Rows[number].Cells[3].Value = (string)((Hashtable)ht[2])["value"]; //description
if (!ht.ContainsValue(3))
{
// Create a Save button column
DataGridViewImageButtonSaveColumn columnSave = new DataGridViewImageButtonSaveColumn();
// Set column values
columnSave.Name = "SaveButton";
columnSave.HeaderText = "";
//Add the columns to the grid
dataGridView1.Rows[number].Cells[4].ReadOnly = false;
dataGridView1.Columns[5].Add(columnSave); //im not sure about this codes
dataGridView1.Rows[number].Cells[6].Value = (string)((Hashtable)ht[4])["value"]; //count
}
else
{
dataGridView1.Rows[number].Cells[4].Value = (string)((Hashtable)ht[3])["value"]; //location
dataGridView1.Rows[number].Cells[5].Value = (string)((Hashtable)ht[4])["value"]; //count
}
However, i'm not sure if I do this right because I receive error at the line commented
dataGridView1.Columns[5].Add(columnSave); //im not sure about this codes
Seems like this code is wrong. Can anyone please advise?
Try dataGridView1.Columns.Insert(5, columnSave); instead.
MSDN reference: DataGridViewColumnCollection.Insert Method
A simple way to insert a checkbox to certain column of Data grid:
DataGridViewCheckBoxColumn chk = new DataGridViewCheckBoxColumn();
dataGridView1.Columns.Insert(**certain column number**, chk);
for example if you want to add checkbox to column 1 you mus type
dataGridView1.Columns.Insert(0, chk);