I am trying to fill a DataTable with values from dynamically created controls. The idea is to have three columns in the DataTable, and fill them one after the other. First fill column 1, then 2, then 3. However, I am struggling to do this effectively.
What is happening is that the DataTable is being filled, but the Data in columns 1, 2 and 3 is not side by side, but instead being filled in different rows. I know this is because I am adding a row with each loop, but I have tried some other things that did not work. Could you help me out?
DataTable tvpIngredients = new DataTable();
tvpIngredients.Columns.Add("Quantity", typeof(string));
tvpIngredients.Columns.Add("Measure", typeof(string));
tvpIngredients.Columns.Add("Ingredient", typeof(string));
foreach (Control ctrlQtt in quantity.Controls)
{
if (ctrlQtt is TextBox)
{
DataRow drNew = tvpIngredients.NewRow();
TextBox quantity = (TextBox)ctrlQtt;
drNew["Quantity"] = quantity.Text;
tvpIngredients.Rows.Add(drNew);
}
}
foreach (Control ctrlMsr in measure.Controls)
{
if (ctrlMsr is DropDownList)
{
DataRow drNew = tvpIngredients.NewRow();
DropDownList measure = (DropDownList)ctrlMsr;
drNew["Measure"] = measure.SelectedValue.ToString();
tvpIngredients.Rows.Add(drNew);
}
}
foreach (Control ctrlIng in ingredient.Controls)
{
if (ctrlIng is TextBox)
{
DataRow drNew = tvpIngredients.NewRow();
TextBox ingredient = (TextBox)ctrlIng;
drNew["Ingredient"] = ingredient.Text;
tvpIngredients.Rows.Add(drNew);
}
}
Thank you!
You already added rows. You need to modify the row column instead.
DataTable tvpIngredients = new DataTable();
tvpIngredients.Columns.Add("Quantity", typeof(string));
tvpIngredients.Columns.Add("Measure", typeof(string));
tvpIngredients.Columns.Add("Ingredient", typeof(string));
foreach (Control ctrlQtt in quantity.Controls)
{
if (ctrlQtt is TextBox)
{
DataRow drNew = tvpIngredients.NewRow();
TextBox quantity = (TextBox)ctrlQtt;
drNew["Quantity"] = quantity.Text;
tvpIngredients.Rows.Add(drNew);
}
}
for (int i = 1; i < tvpIngredients.Rows.Count; i++)
{
if (ctrlMsr is DropDownList)
{
DataRow drNew = tvpIngredients.NewRow();
DropDownList measure = (DropDownList)ctrlMsr;
drNew["Measure"] = measure.SelectedValue.ToString();
tvpIngredients.Rows[i][1].ToString() = drNew;
}
}
for (int i = 1; i < tvpIngredients.Rows.Count; i++)
{
if (ctrlIng is TextBox)
{
DataRow drNew = tvpIngredients.NewRow();
TextBox ingredient = (TextBox)ctrlIng;
drNew["Ingredient"] = ingredient.Text;
tvpIngredients.Rows[i][2].ToString() = drNew;
}
}
To solve a problem like this you need to think about the way you model your data.
Looking at the code here I can guess that you have a Form let's call it Recipe that has at least 3 controls quantity, measure, and ingredients. These controls can contain an arbitrary number of items. It looks like they would be of equal length given that they are each related to one item in your recipe.
You could do this:
if(quantity.Controls.Count != measure.Controls.Count
|| quantity.Controls.Count != ingredient.Controls.Count)
{
throw new Exception("Invalid ingredients list");
}
var quantities = new Control[quantity.Controls.Count];
var measurements = new Control[measure.Controls.Count];
var ingredients = new Control[ingredient.Controls.Count];
quantity.Controls.CopyTo(quantities, 0);
measure.Controls.CopyTo(measurements , 0);
ingredient.Controls.CopyTo(ingredients , 0);
for(var i = 0; i < quantity.Length; ++i)
{
// Make your new row
// index the arrays and fill the row data in
}
There is probably a more elegant solution to the whole problem if you spend some time thinking through how you model it.
Related
//...
{
public Form1()
{
InitializeComponent();
LoadData();
textBoxFill();
}
private void LoadData()
{
SqlConnection SCConnect = new SqlConnection("Server=localhost;Initial Catalog=T8;Integrated Security=SSPI;");
SCConnect.Open();
StringBuilder SBBuilder = new StringBuilder("Select * from Table8");
SqlDataAdapter SDA = new SqlDataAdapter(SBBuilder.ToString(), SCConnect);
SqlCommandBuilder SCB = new SqlCommandBuilder(SDA);
DataTable DT = new DataTable();
SDA.Fill(DT);
dataGridView1.DataSource = DT;
}
private void textBoxFill()
{
TextBox TB = new TextBox();
int A = 1;
for (int i = 0; i < dataGridView1.Columns.Count; i++)
{
panel1.Controls.Add(TB);
TB.Location = new Point(10, (A * 20));
TB.Top = A * 28;
TB.Size = new Size(200, 50);
TB.Margin = new Padding(10, 10, 10, 10);
}
A = A + 1;
}
}
How do I add multiple TextBox follow by DataGridView.Columns.Count and
each TextBox to fill in each DataGridView columns data.TQ?
I am guessing after looking at the previous duplicate post, that this may be what you are looking for. It may help you if you explained the overall picture as this seems like on odd thing to do since the data is already in the grid and the user can edit it, I am not sure why you would do this data “duplication” in the panel.
However, it does appear you want to have the textboxes correspond to the currently “selected” row in the grid. Such that there will be one textbox for each column in the grid. Initially, you do know how many columns the data may contain. Therefore, you need to dynamically create the textbox’s in the panel.
One approach to “bind” each textbox to a column of the currently selected row in the grid may be accomplished by “binding” each textbox to a particular column in the DataTable that is used as the DataSource to the grid. Each textbox has a property called…DataBindings. This property will allow you to “bind” the textbox to a particular column in the DataTable. Below is an example.
To help, given we have the data, I suggest a method AddTextBoxesToPanel(DataTable dt) … that takes a DataTable and loops through the columns of that table and creates a textbox for each column AND adds the “binding” for that column to that textbox. With this approach, no extra code will be necessary to fill the text boxes when the user selects different rows.
private void AddTextBoxesToPanel(DataTable dt) {
panel1.Controls.Clear();
panel1.AutoScroll = true;
panel1.AutoScrollMinSize = new Size(0, (dt.Columns.Count * 23) + 15);
TextBox curTB;
int y = 10;
foreach (DataColumn col in dt.Columns) {
curTB = GetTextBox(10, y);
curTB.DataBindings.Add(new Binding("Text", dt, col.ColumnName));
panel1.Controls.Add(curTB);
y += 23;
}
}
Above, we assume this may be called more than once and need to “clear” any previous textboxs in the panel. Set the panel to be scrollable, then start the loop through the columns to add the textboxes to the panel. The GetTextBox method (below) simply gets a new TextBox with the desired location. Lastly, we set the DataBinding for “that” textbox to point to “that” column. curTB.DataBindings.Add(new Binding("Text", dt, col.ColumnName));
private TextBox GetTextBox(int xLoc, int yLoc) {
TextBox TB = new TextBox {
Text = "",
Location = new Point(xLoc, yLoc),
Size = new Size(150, 50),
Margin = new Padding(10),
Anchor = AnchorStyles.Left
};
return TB;
}
Below is a complete example using the above method. The Forms Load method to fill a DataTable with 10 columns and 20 rows, then use that DataTable as a DataSource to the grid. Then call the method above to set the textboxes into the panel.
private void Form1_Load(object sender, EventArgs e) {
FillGrid(10, 20);
AddTextBoxesToPanel((DataTable)dataGridView1.DataSource);
}
A method to generate some data for testing.
private void FillGrid(int totalColumns, int totRows) {
DataTable dt = new DataTable();
// add columns
for (int i = 0; i < totalColumns; i++) {
dt.Columns.Add("Col" + i, typeof(string));
}
// add rows
object[] data = new object[totalColumns];
for (int row = 0; row < totRows; row++) {
for (int col = 0; col < totalColumns; col++) {
data[col] = "Col" + col + "Row" + row;
}
dt.Rows.Add(data);
}
dataGridView1.DataSource = dt;
}
Hope this helps.
I am trying to scrape data from the webpage. However, I am having a trouble scraping all of data in the table. I need to switch pages to get all the data and I am willing to get an output with DataGridTable. I am having a trouble figuring out how to do this even though there is a change with number of pages they have in the website. I would like to add information automatically on a data grid table pages by pages. My input(Website) is only showing 25 items. Thats why I have 25 items in DataGridTable. I would like to justify a "number of pages" from "go to end page button"'s element. So that my program knows how many pages are there to scrape from the website. but, if there's a different way, I wanna know thank you.
This is my code for now.
DataTable dt = new DataTable();
var header = driver.FindElement(By.CssSelector("#gridComponent > div.k-grid-header"));
foreach (var row in header.FindElements(By.TagName("tr")))
{
//Configure Number of Col and row
int cellIndex = 0;
string[] arr = new string[32];
//Get Cell Data
foreach (var cell in row.FindElements(By.TagName("th")))
{
// Check the header cell for a checkbox child. If no
// such child exists, add the column.
var headerCheckboxes = cell.FindElements(By.CssSelector("input[type='checkbox']"));
if (headerCheckboxes.Count == 0)
{
//Number of Col Data Load
if (cellIndex <= 29)
{
arr[cellIndex] = cell.Text;
dt.Columns.Add(cell.Text);
}
else
cellIndex++;
}
}
Console.WriteLine(arr);
}
var table = driver.FindElement(By.CssSelector("#gridComponent"));
//Get Row value
foreach (var row in table.FindElements(By.TagName("tr")))
{
//Configure Number of Col and row
int cellIndex = 0;
// Use a list instead of an array
List<string> arr = new List<string>();
//Get Cell Data
foreach (var cell in row.FindElements(By.TagName("td")))
{
// Skip the first column in the row by checking
// if the cell index is 0.
if (cellIndex != 0)
{
string cellValue = "";
Console.WriteLine(cell);
var checkboxes = cell.FindElements(By.CssSelector("input[type='checkbox']"));
if (checkboxes.Count > 0)
{
bool isChecked = false;
isChecked = checkboxes[0].Selected;
cellValue = isChecked.ToString();
}
else
{
cellValue = cell.Text;
}
arr.Add(cellValue);
}
cellIndex++;
}
dt.Rows.Add(arr.ToArray());
}
dataGridView1.DataSource = dt;
driver.FindElement(By.CssSelector("#gridComponent > div.k-pager-wrap.k-grid-pager.k-widget.k-floatwrap > ul > li:nth-child(3)")).Click();
}
This is the table that I am trying to scrape from.
This is the code for the following element that is shown picture above.
<span class="k-icon k-i-arrow-end-right"></span>
Thank you so much.
You may want to consider the index information "1 - 25 out of 64 items", since it is a good indicator of the total number of pages.
Batch = 1 - 25 i.e. 25 items per page
Total items = 64
No. of pages = roundup (64 / 25)
PS: A better option, without any computations, maybe to get the "data-page" attribute of the last page button.
I Finally got the answer for this.
private List<List<string>> GetRecords(IWebElement table)
{
List<List<string>> rows = new List<List<string>>(); ;
//Get Row value
foreach (var row in table.FindElements(By.TagName("tr")))
{
//Configure Number of Col and row
int cellIndex = 0;
// Use a list instead of an array
List<string> cols = new List<string>();
//Get Cell Data
foreach (var cell in row.FindElements(By.TagName("td")))
{
// Skip the first column in the row by checking
// if the cell index is 0.
if (cellIndex != 0)
{
string cellValue = "";
Console.WriteLine(cell);
var checkboxes = cell.FindElements(By.CssSelector("input[type='checkbox']"));
if (checkboxes.Count > 0)
{
bool isChecked = false;
isChecked = checkboxes[0].Selected;
cellValue = isChecked.ToString();
}
else
{
cellValue = cell.Text;
}
cols.Add(cellValue);
}
cellIndex++;
}
rows.Add(cols);
}
return rows;
}
private void button1_Click(object sender, EventArgs e)
{
//Configure to Hide CMD
var chromeDriverService = ChromeDriverService.CreateDefaultService();
chromeDriverService.HideCommandPromptWindow = true;
//Configure to Hide Chrome
ChromeOptions option = new ChromeOptions();
option.AddArgument("--headless");
//HIDING CHROME UN-COMMNET THE SECOND ONE TO SHOW
//IWebDriver driver = new ChromeDriver(chromeDriverService, option);
IWebDriver driver = new ChromeDriver();
driver.Url = "**************";
driver.Manage().Window.Maximize();
driver.SwitchTo().DefaultContent();
//Log-in
driver.FindElement(By.Id("username")).SendKeys("*****");
driver.FindElement(By.Id("password")).SendKeys("******" + OpenQA.Selenium.Keys.Enter);
//Entering Access Code
driver.FindElement(By.Id("password")).SendKeys("*******");
driver.FindElement(By.Id("accesscode")).SendKeys("********" + OpenQA.Selenium.Keys.Enter);
//go to CustomerList
driver.Navigate().GoToUrl("***********");
driver.Navigate().GoToUrl("*****************");
//Wait till load 3 seconds
waitOnPage(2);
DataTable dt = new DataTable();
var header = driver.FindElement(By.CssSelector("#gridComponent > div.k-grid-header"));
foreach (var row in header.FindElements(By.TagName("tr")))
{
//Configure Number of Col and row
int cellIndex = 0;
string[] arr = new string[32];
//Get Cell Data
foreach (var cell in row.FindElements(By.TagName("th")))
{
// Check the header cell for a checkbox child. If no
// such child exists, add the column.
var headerCheckboxes = cell.FindElements(By.CssSelector("input[type='checkbox']"));
if (headerCheckboxes.Count == 0)
{
//Number of Col Data Load
if (cellIndex <= 29)
{
arr[cellIndex] = cell.Text;
dt.Columns.Add(cell.Text);
}
else
cellIndex++;
}
}
Console.WriteLine(arr);
}
var table = driver.FindElement(By.CssSelector("#gridComponent"));
List<List<string>> records = GetRecords(table);
// Supposing you want the footer information
var lastPageStr = table.FindElement(By.ClassName("k-pager-last")).GetAttribute("data-page");
var lastPage = Convert.ToInt16(lastPageStr);
// You can select other info lik this
// class="k-link k-pager-nav" data-page="1"
driver.FindElement(By.CssSelector("#gridComponent > div.k-pager-wrap.k-grid-pager.k-widget.k-floatwrap > ul > li:nth-child(3)")).Click();
// Cycle over the pages
for (int p = 0; p < (lastPage - 1); p++)
{
driver.FindElement(By.CssSelector("#gridComponent > div.k-pager-wrap.k-grid-pager.k-widget.k-floatwrap > a:nth-child(4) > span")).Click();
waitOnPage(2);
var rows = GetRecords(table);
records.AddRange(rows);
}
// Add all rows to DT
//dt.Rows.Add(records[4].ToArray());
foreach(var row in records)
{
dt.Rows.Add(row.ToArray());
}
dataGridView1.DataSource = dt;
}
I am getting a error when I am trying to get a specific column from my table in the datagridview.
Here is how I populate the table----
public DataTable createGridForForm(int rows, int columns)
{
// Create the output table.
DataTable table = new DataTable();
for (int i = 1; i <= columns; i++)
{
table.Columns.Add("column " + i.ToString());
}
for (int i = 1; i < rows; i++)
{
DataRow dr = table.NewRow();
// populate data row with values here
table.Rows.Add(dr);
}
return table;
}
And here is how i create the datagridview------
private void createGridInForm(int rows, int columns)
{
DataGridView RunTimeCreatedDataGridView = new DataGridView();
RunTimeCreatedDataGridView.DataSource = createGridForForm(rows, columns);
DataGridViewColumn ID_Column = RunTimeCreatedDataGridView.Columns[0];
ID_Column.Width = 200;
int positionForTable = getLocationForTable();
RunTimeCreatedDataGridView.Size = new Size(800, 200);
RunTimeCreatedDataGridView.Location = new Point(5, positionForTable);
myTabPage.Controls.Add(RunTimeCreatedDataGridView);
}
The error I am getting is that the Index was out of range. It may not be negative and must be smaller than the size. What I am trying to do is that I'm getting a table from a text file and then in run time I am showing it in my form, but the table doesn't match my data grid view in size, it doesn't look good. So I want to make the table fit the Data grid view.
Try-
DataGridViewColumn ID_Column = dataGridView1.Columns[0];
ID_Column.Width = 200;
I have a datatable containing over 100 columns, how ever I need to strip out all columns
except first 11 columns.
I need to retain data of 1st 11 columns.
I am doing it with following code
public DataTable validdatatable(DataTable table)
{
DataTable dt = new DataTable();
for (int i = 0; i < 11; i++)
{
DataColumn dc = new DataColumn();
dc.ColumnName = table.Columns[i].ColumnName;
dc.DataType = table.Columns[i].DataType;
dt.Columns.Add(dc);
}
for (int i = 0; i < table.Rows.Count; i++)
{
object[] ob = table.Rows[i].ItemArray;
...
...
}
return dt;
}
This methods works but is too heavy on CPU and Ram.
Is there any other method with which I can proceed?
Try this:
public DataTable validdatatable(DataTable table)
{
var dt = table.Columns.Cast<DataColumn>().Take(11);
return dt.CopyToDataTable();
}
Or Something like this. It will give you at least a way to work on it.
Note that You need to add a reference to the assembly: System.Data.DataSetExtensions.dll then you can write your function like above.
You can try this. The only difference would be instead of object[] ob = table.Rows[i].ItemArray it will just grab the first 11 columns using the index and make an array out of that (itemArray will make an array of all 100 columns). Still doubt this will solve your memory issues if you are that tight but it's probably worth a shot.
var copyDt = new DataTable();
for (var i = 0; i < 11; i++)
{
copyDt.Columns.Add(dataTable.Columns[i].ColumnName, dataTable.Columns[1].DataType);
}
copyDt.BeginLoadData();
foreach (DataRow dr in dataTable.Rows)
{
copyDt.Rows.Add(Enumerable.Range(0, 11).Select(i => dr[i]).ToArray());
}
copyDt.EndLoadData();
my DataTable has over 1000 columns and I want to display values on the datagridview. Because of the FillWeigth problem I use the following method to fill the gridview,
public bool TransferDataTableToGrid(DataGridView dataGrid, DataTable dataTable)
{
dataGrid.SuspendLayout();
if ((dataGrid != null) && (dataTable != null))
{
dataGrid.Columns.Clear();
dataGrid.AutoGenerateColumns = false;
dataGrid.DataSource = dataTable;
for (int i = 0; i < dataTable.Columns.Count; i++)
{
DataGridViewColumn column = new DataGridViewColumn();
column.Name = dataTable.Columns[i].ColumnName;
column.FillWeight = 1;
column.CellTemplate = new DataGridViewTextBoxCell();
column.ValueType = dataTable.Columns[i].DataType;
dataGrid.Columns.Add(column);
}
for (int i = 0; i < dataTable.Columns.Count; i++)
{
for (int ii = 0; ii < dataTable.Rows.Count; ii++)
{
dataGrid[i, ii].Value = dataTable.Rows[ii][i];
}
}
}
dataGrid.ResumeLayout();
return true;
}
and sometimes I have an effect that my gridview is empty. Only after second execution data is displayed. Do you have any ideas, why...?
Thanks.
I recommend to use paging, i mean that you can show about 20 columns with navigation buttons
under your grid, it's like Google or others... even your are not programming a web application.
Use binding source to fill your grid
SqlDataAdapter adapter = new SqlDataAdapter(database.cmd);
dataSet1.Tables.Clear();
adapter.Fill(dataSet1, "Table");
bs = new BindingSource();
bs.DataSource = dataSet1.Tables["Table"];
dataGridView1.DataSource = bs;
now you dont need to worry about creating columns and fill cells in loops and its much better performance
Bind Data to Datagridview
Well, I solved my problem. With Ivan's suggestion I tried the alternative way to fill data: instead of using DataSource I add new rows manually
foreach (DataRow row in dataTable.Rows)
{
var dataGridRow = new DataGridViewRow();
dataGridRow.CreateCells(dataGrid);
for (int i = 0; i < row.ItemArray.Length; i++)
{
dataGridRow.Cells[i].Value = row.ItemArray[i];
}
dataGrid.Rows.Add(dataGridRow);
}
...and it works - data in dgv is displayed. Thanks!