Merging cell of equal value in ASP.NET Repeater - c#

Currently I try to use Repeater WebControl in order to display a table that list out all possible component. Below is my table;
Right now I try to merged cells in Group Code and Group Description column that have the same value. Below is my merging cell code, noted that the code is in class;
public void repeaterRowSpan(string repeaterID, string columnID)
{
var pageHandler = HttpContext.Current.CurrentHandler;
Control ctrl = ((Page)pageHandler).Master.FindControl("ContentPlaceHolder3").FindControl(repeaterID);
Repeater repeaterName = (Repeater)ctrl;
for (int i = repeaterName.Items.Count - 1; i > 0; i--)
{
HtmlTableCell oCell_previous = (HtmlTableCell)repeaterName.Items[i - 1].FindControl(columnID);
HtmlTableCell oCell = (HtmlTableCell)repeaterName.Items[i].FindControl(columnID);
oCell.RowSpan = (oCell.RowSpan == -1) ? 1 : oCell.RowSpan;
oCell_previous.RowSpan = (oCell_previous.RowSpan == -1) ? 1 : oCell_previous.RowSpan;
if (oCell.InnerText == oCell_previous.InnerText)
{
oCell.InnerText = "";
oCell_previous.RowSpan += oCell.RowSpan;
}
}
}
Somehow the code manage to delete the same value in the column but maintain the rowspan. When I debugged, the oCell_previous.RowSpan return '2' so the code itself working fine. Below is the result of merging;
How can I modified my code in such way it will merged the cell?
In your opinion, between Repeater and GridView which is most suitable to show data in table form in this project? In my understanding, Repeater is most suitable since it faster than GridView. GridView is only suitable if you have edit function to go with your table.

This can be implemented using the OnDataBound event. The OnDataBound event of the GridView is executed after the GridView is populated with the records. Executed a loop in reverse over the GridView Rows and then the common Cells are identified and merged into single cell.
Below sample code merges the first & second columns (assuming that there are redundant values by comparing the above row(s)),
Feel free to leave a comment if you need more info.
protected void OnDataBound(object sender, EventArgs e)
{
for (int i = GridView1.Rows.Count - 1; i > 0; i--)
{
GridViewRow row = GridView1.Rows[i];
GridViewRow previousRow = GridView1.Rows[i - 1];
for (int j = 0; j < row.Cells.Count; j++)
{
if (row.Cells[j].Text == previousRow.Cells[j].Text)
{
if (previousRow.Cells[j].RowSpan == 0)
{
if (row.Cells[j].RowSpan == 0)
{
previousRow.Cells[j].RowSpan += 2;
}
else
{
previousRow.Cells[j].RowSpan = row.Cells[j].RowSpan + 1;
}
row.Cells[j].Visible = false;
}
}
}
}
}

Related

How to add a value at the first empty cell in column?

In my application, I have a datagridview. For each column, there is a button. If clicked, I take the column index and now want to add a user enterd value at the first (bottom or top doesn't matter) free cell of that column. If there is no empty cell, I want to create one.
I tried looping through all rows, getting the cell values and checking if they are empty.
string data = "%VALUE%";
int rowIndex = -1;
foreach (DataGridViewRow row in dataGridViewTasks.Rows)
{
data = (string) row.Cells[columnIndex].Value;
if (string.IsNullOrWhiteSpace(data))
{
rowIndex = row.Index;
break;
}
else if (row.Cells[columnIndex].RowIndex == dataGridViewTasks.Rows.Count - 1)
{
rowIndex = dataGridViewTasks.Rows.Add();
break;
}
}
if (string.IsNullOrWhiteSpace(data) && rowIndex > -1)
{
dataGridViewTasks[columnIndex, rowIndex].Value = task.name;
}
Problem with this solution is:
If for example row 1 and 2 of column A are filled, a new value in column B is added in row 3. So that is not what I want.
If I understand you correctly, it`s should work.
Try it:
private void Button1_Click(object sender, EventArgs e)
{
var columnIndex = 1;
var dataGrid = new DataGridView();
var index = GetFirstEmptyCellByColumnIndex(dataGrid, columnIndex);
dataGrid[columnIndex, index].Value = "new value";
}
private int GetFirstEmptyCellByColumnIndex(DataGridView dataGrid, int columnIndex)
{
var index = -1;
foreach (DataGridViewRow row in dataGrid.Rows)
{
if (string.IsNullOrEmpty((string)row.Cells[columnIndex].Value))
{
index = row.Index;
break;
}
}
return index != -1
? index
: dataGrid.Rows.Add();
}

Events functionality should apply on all pages in datagridview - Paging concept

I have a task to develop Windows applications where paging is involved. If I perform any event like splitting date and time, it's applied only to the current page. I would like to apply that event to all pages in the Datagridview.
If I take a datatable/dataset and work on it, the UI is taking time to read the file as it again reads the whole file to data table. So, please suggest any other alternative to apply the events to all pages in the DataGridView.
I will post the code, or upload my code in any site or here, if required.
Please let me know if my question is unclear.
VARIABLES DECLARATION:
List<String> cmbList = new List<string>();
public String Replace;
public String Find;
public String Col;
public String NewColumn;
public String NewColumnValue;
public string MyFOrmat { get; set; }
int PageCount;
int maxRec;
int pageSize = 30;
int currentPage = 1;
int recNo = 0;
string FileName;
String[] datfile;
button1 = BROWSE BUTTON (Where i read the file):
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog1 = new OpenFileDialog();
openFileDialog1.InitialDirectory = "Desktop";
openFileDialog1.Filter = "dat files (*.DAT)|*.DAT|All files (*.*)|*.*";
openFileDialog1.FilterIndex = 2;
openFileDialog1.RestoreDirectory = true;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
try
{
FileName = openFileDialog1.FileName;
string text = System.IO.File.ReadAllText(FileName);
datfile = text.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
//Added on 2015-12-02
maxRec = datfile.Length - 1;
PageCount = maxRec / pageSize;
LoadPage(MyFOrmat);
}
catch (Exception ex)
{
MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
}
}
}
LOADPAGE Code:
public void LoadPage(string Format, bool isFindAndReplace = false)
{
int startRec;
int endRec;
if (currentPage == PageCount)
{
endRec = maxRec;
}
else
{
endRec = pageSize * currentPage;
}
dataGridView1.Rows.Clear();
if (recNo == 0)
{
dataGridView1.Columns.Clear();
}
int rowindex = 0;
startRec = recNo;
for (int RowCount = startRec; RowCount <= endRec; RowCount++)
{
if (datfile[RowCount].ToString() != "" )
{
if (RowCount == 0)
{
string[] column = datfile[RowCount].Split('þ');
for (int i = 0; i < column.Length - 1; i++)
{
if (column[i].ToString() != "" && column[i].ToString() != "\u0014")
{
DataGridViewTextBoxColumn dgvtxtcountry = new DataGridViewTextBoxColumn();
dgvtxtcountry.HeaderText = column[i].ToString();
dgvtxtcountry.Name = column[i].ToString();
dataGridView1.Columns.Add(dgvtxtcountry);
cmbList.Add(column[i]);
i += 1;
}
}
}
if (RowCount != 0)
{
dataGridView1.Rows.Add();
string[] column = datfile[RowCount].Split('þ');
int index = 0;
for (int i = 1; i < column.Length - 1; i++)
{
if (column[i].ToString() != "\u0014")
{
if (i == 3)
{
dataGridView1.Rows[rowindex].Cells[index].Value = Convert.ToDateTime(column[i]).ToString(Format);
}
else
{ dataGridView1.Rows[rowindex].Cells[index].Value = column[i].Trim('þ'); }
index += 1;
i += 1;
}
}
rowindex += 1;
}
}
recNo += 1;
}
}
FIND and REPLACE Event:
private void btnFindandReplace_Click(object sender, EventArgs e)
{
Form2 f = new Form2();
f.cmbColumnCombo.DataSource = cmbList;
f.ShowDialog();
for (int i = 0; i <= dataGridView1.Rows.Count - 1; i++)
{
//dataGridView1.Rows[rowindex].Cells[index].Value = Convert.ToDateTime(column[i]).ToString(Format);
if (dataGridView1.Rows[i].Cells[f.cmbColumnCombo.Text].Value.ToString().ToLower().Contains(f.txtfind.Text.ToLower()))
{
//dataGridView1.Rows[i].Cells[f.cmbColumnCombo.Text].Value = dataGridView1.Rows[i].Cells[f.cmbColumnCombo.Text].Value.ToString().ToLower().Replace(f.txtfind.Text.ToLower(), f.txtreplace.Text);
//bulidDataRow(i);
if (!string.IsNullOrEmpty(f.txtfind.Text))
{
dataGridView1.Rows[i].Cells[f.cmbColumnCombo.Text].Value = dataGridView1.Rows[i].Cells[f.cmbColumnCombo.Text].Value.ToString().Replace(f.txtfind.Text, f.txtreplace.Text);
#region Commented
//dataGridView1.Rows[i].Cells[f.cmbColumnCombo.Text].Value = dataGridView1.Rows[i].Cells[f.cmbColumnCombo.Text].Value.ToString().Replace(f.txtfind.Text, f.txtreplace.Text);
//bulidDataRow(i);
#endregion
}
}
}
}
private void btnNext_Click(object sender, EventArgs e)
{
currentPage += 1;
if (currentPage > PageCount)
{
currentPage = PageCount;
//Check if you are already at the last page.
if (recNo == maxRec)
{
MessageBox.Show("You are at the Last Page!");
return;
}
}
LoadPage(MyFOrmat);
}
Please let me know if anything needs to be added.
To summarize your requirements:
You want to read largish data files of 10k - 500k records
You want to display them in chunks/pages in a DataGridView
You want allow the user to modify the data:
The user can merge columns
The user can use change&replace on the data
Date&time columns may be split
Possibly modified data shall be saved
The way I see it you have two approaches:
Either cache the data
Or cache the actions
Caching the actions is doable but clearly a lot more fuss, both in coding the caching and in keeping the data synchronized.
So caching the data would be my first choice.
Here is a sketch of how to break up the functionality:
A function to read in the whole data and load them into a DataTable
Functions for the initial display and for displaying a certain page
Functions for doing each of the changes on the list of rows.
After calling a changing function the current page display must be refreshed.
Keeping the total quantity of data in memory shouldn't really be a problem today; I notice that you are reading in all data as strings already in the datFile array. Reading it into a table will spare you to split it over and over..
A DataTable.DataRow also offers nice properies like HasErrors or RowState. And its Items can have a dedicated type to help with formatting..
Note however that DataRow doesn't have a (real) constructor; instead it must be created from a DataTable, so you will first have to create one from your columns!
The display code would use a pageSize and a currentFirstLine variable; it can clear and add the rows into the DGV or you could go for a binding solution with the DataTable you need anyway holding the DataRows and a filter on the table or rather on an BindingSource.
Of course you can also use a structure of your own, maybe a simple as a string[] or a List<string>to hold the row data..
If you are interested in the idea of caching the actions, you could create a ChangeAction class that holds:
the type
the parameters needed, ie, the column(s), the change&replace strings etc..
Then in a List<ChangeAction> you would store them as they happen and then apply them to each unchanged row. But here comes the first catch: You will need to know which row have been changed and maybe if a ChangeAction can be applied twice without screwing up the data.. More problems may or may not come later, depending on the details of you data and actions..
Here is an example of how to set up the binding using class level variables:
DataTable DT = new DataTable();
BindingSource BS = new BindingSource();
int pageSize = 0;
int firstLineVisible = 0;
After filling the table you can bind it and set the initial filer:
BS.DataSource = DT;
dataGridView1.DataSource = BS;
pageSize = (dataGridView1.ClientSize.Height - dataGridView1.ColumnHeadersHeight)
/ dataGridView1.Rows[0].Height;
int l1 = firstLineVisible; int l2 = firstLineVisible + pageSize;
BS.Filter = "Nr >= " + l1 + " and Nr < " + l2;
When scrolling you simply change the firstLineVisible and rest the Filter and the DataSource..
Now all your data modifications should work on the data in the DataTable using the SetField method!
Also note that you need one column in your data that holds a running number. If your data don't have one it is easy to include it by adding it to the data lines:
The column gets autogenerated in the DataGridView. For the DataTable we want to have it in the first data line; I use a separator string sep:
var lines = File.ReadAllLines(fileName).ToList();
..
string[] sep = { ";" };
var p0 = ("Nr" + sep[0] + lines[0]).Split(sep, StringSplitOptions.None );
DT.Columns.Clear();
for (int i = 0; i < p0.Length; i++) DT.Columns.Add(p0[i], typeof(string));
Adding it to the data is just as simple:
for (int l = 1; l < lines.Count; l++)
{
var p = (l + sep[0] + lines[l]).Split(sep, StringSplitOptions.None);
DT.Rows.Add(p);
}
You can hide the number column if you want to..:
dataGridView1.Columns["Nr"].Visible = false;
You should add that line right after setting the Filter.

DataTable normalize blanks cells

I have not found a method to normalize a DataTable that came from an Excel with merged cells. When I get the DataTable from that Excel, only the first cell has the value, others are blank.
An example of this DataTable is:
and the expected result:
To summarize: blanks cells should be completed with the value of the next cell above with a value, since is what was happened with the Excel merge of cells.
I'm using Excel.dll to read this Excel, didn't provide the autofill of cells, so that's why I'm searching for a method inside C#.
I suppose that logic should be: if a cell is blank, use the upper cell as a value. The logic appears clear but I have issues trying to get the code to apply it.
This is a sample, but at the end, I'm looking for a method to do this whenever columns or rows have the datatable.
Edit:
Thanks for your quicky feedback.
Attached what i have so far for just only one column and with errors since doesn't take care of the first and last row, but is the idea... what i try to achieve is to have a method for any amount of cols and rows (could be ok if cols are fixed with names, and then if i have more columns i will adapt).
private void NormalizeDataTable(DataTable dtRawTable)
{
DataTable dtFinalized = new DataTable();
dtFinalized.Columns.Add("Col1", typeof(String));
string previousValue = "";
for (int index = 0; index <= dtRawTable.Rows.Count; index++)
{
DataRow dr = dtFinalized.NewRow();
if (index != 0 || index == dtRawTable.Rows.Count -1)
{
if (dtRawTable.Rows[index]["Modelo"].ToString() == "")
{
dr["Col1"] = previousValue;
}
else
{
dr["Col1"] = Convert.ToString(dtRawTable.Rows[index]["Modelo"].ToString());
previousValue = (string)dr["Col1"];
}
}
dtFinalized.Rows.Add(dr);
dtFinalized.AcceptChanges();
}
}
Here is the function i using in my project for same requirement.
public static DataTable AutoFillBlankCellOfTable(DataTable outputTable)
{
for (int i = 0; i < outputTable.Rows.Count; i++)
{
for (int j = 0; j < outputTable.Columns.Count; j++)
{
if (outputTable.Rows[i][j] == DBNull.Value)
{
if (i > 0)
outputTable.Rows[i][j] = outputTable.Rows[i - 1][j];
}
}
}
return outputTable;
}

C# .Rows and .Cells doesn't work

I'm using a ASP.NET GridView and now I want to define the colspan automatically.
I had th VB code and I converted it to C#. My code is currently looking like that, but the .Cells and .Rows aren't working. Does anyone know what's wrong. Am I not allowed to use these?
+ I found the right namespace: System.Windows.Forms
but then I also need to use
System.Windows.Forms.DataGridViewCellCollection; but where do I put it?
protected void grdvCamp_DataBound1(object sender, EventArgs e)
{
for (int rowIndex = grdvCamp.Rows.Count - 2; rowIndex >= 0; rowIndex += -1)
{
GridViewRow gvRow = grdvCamp.Rows(rowIndex);
GridViewRow gvPreviousRow = grdvCamp.Rows(rowIndex + 1);
for (int cellCount = 0; cellCount <= gvRow.Cells.Count - 9; cellCount++)
{
if (gvRow.Cells(cellCount).Text == gvPreviousRow.Cells(cellCount).Text)
{
if (gvPreviousRow.Cells(cellCount).RowSpan < 2)
{
gvRow.Cells(cellCount).RowSpan = 2;
}
else
{
gvRow.Cells(cellCount).RowSpan = gvPreviousRow.Cells(cellCount).RowSpan + 1;
}
gvPreviousRow.Cells(cellCount).Visible = false;
}
}
}
}
I think the right syntax should be
.Rows[rowIndex]
.Cells[cellCount]
instead of
.Rows(rowIndex)
.Cells(cellCount)
since both returns a collection. As far as I know, () syntax uses in vb.net but [] uses in C#. When you write grdvCamp.Rows(rowIndex) in C#, it looks Rows as a method of grdvCamp, not a collection.
For more information;
GridView.Rows property
TableRows.Cells property

Add and find Dropdownlist at Runtime inside GridView

In my asp.net application, I have used Gridview control, In which i have to add Dropdownlist at runtime for each cell.Which i am able to bind successfully.
Below is my code which inside row databound event,
foreach (GridViewRow row in gdvLocation.Rows) {
if (row.RowType == DataControlRowType.DataRow) {
for (int i = 1; i < row.Cells.Count; i++) {
var dlRouteType = new DropDownList();
dlRouteType.ID = "ddlRouteType";
dlRouteType.DataSource = GetRouteTypeList();
dlRouteType.DataTextField = "RouteType";
dlRouteType.DataValueField = "Id";
dlRouteType.DataBind();
row.Cells[i].Controls.Add(dlRouteType);
}
}
}
I have a button in my page, which has functionality to save data to database . While saving data i have to pass the value from Dropdownlist which i have added at runtime. On button click i am writing following code to get data from dropdownlist,
var ddlDropDown = (DropDownList)row.Cells[i].FindControl("ddlRouteType");
But i am getting null in ddlDropDown object. I have even added Update panel inside aspx page. Any suggessions most welcome.
Thanks in advance
Sangeetha
You have these errors in your code
RowDataBound already iterates through each rows and so all you need not write that foreach on top
You are iterating from index 1, index are zero-based. So start from zero.
The DropDownList ID must be unique, so better write something like,dlRouteType.ID = "ddlRouteType_" + i;
The code should be,
protected void gdvLocation_RowDataBound(object sender, GridViewRowEventArgs e)
{
//removed the foreach loop
var row = e.Row;
if (row.RowType == DataControlRowType.DataRow)
{
for (int i = 0; i < row.Cells.Count; i++) //changed index
{
var dlRouteType = new DropDownList();
dlRouteType.ID = "ddlRouteType_" + i; //gave unique id
dlRouteType.DataSource = GetRouteTypeList();
dlRouteType.DataTextField = "RouteType";
dlRouteType.DataValueField = "Id";
dlRouteType.DataBind();
row.Cells[i].Controls.Add(dlRouteType);
}
}
}

Categories

Resources