I'm using Epplus to render an Excel spreadsheet into HTML. So far it's going very, very well, except for one thing... spanning merged cells. I just can't seem to get the logic right. I thought I would throw it out there to see how the community would deal with it. Here is my code so far.
public String ParseExcelStamps(String FileName)
{
FileInfo theFile = new FileInfo(FileName);
String html = "";
using (ExcelPackage xlPackage = new ExcelPackage(theFile))
{
var workbook = xlPackage.Workbook;
if (workbook != null)
{
for (int j = 1; j <= workbook.Worksheets.Count; j++)
{
Tab tab = new Tab();
html+= "<table style='border-collapse: collapse;font-family:arial;'><tbody>";
var worksheet = workbook.Worksheets[j];
tab.Title = worksheet.Name;
if (worksheet.Dimension == null) { continue; }
int rowCount = 0;
int maxColumnNumber = worksheet.Dimension.End.Column;
var convertedRecords = new List<List<string>>(worksheet.Dimension.End.Row);
var excelRows = worksheet.Cells.GroupBy(c => c.Start.Row).ToList();
excelRows.ForEach(r =>
{
rowCount++;
html += String.Format("<tr>");
var currentRecord = new List<string>(maxColumnNumber);
var cells = r.OrderBy(cell => cell.Start.Column).ToList();
Double rowHeight = worksheet.Row(rowCount).Height;
for (int i = 1; i <= maxColumnNumber; i++)
{
var currentCell = cells.Where(c => c.Start.Column == i).FirstOrDefault();
//look aheads for colspan and rowspan
ExcelRangeBase previousCellAbove = null;
ExcelRangeBase previousCell = null;
ExcelRangeBase nextCell = null;
ExcelRangeBase nextCellBelow = null;
try { previousCellAbove = worksheet.Cells[rowCount-1, i]; }catch (Exception) { }
try { previousCell = worksheet.Cells[rowCount, (i - 1)]; }catch (Exception) { }
try { nextCell = worksheet.Cells[rowCount, (i + 1)]; }catch (Exception) { }
try { nextCellBelow = worksheet.Cells[rowCount+1, i]; }catch (Exception) { }
if ((previousCell != null) && (previousCell.Merge) && (currentCell != null) && (currentCell.Merge)){continue;}
if ((previousCellAbove != null) && (previousCellAbove.Merge) && (currentCell != null)) {continue; }
if (currentCell == null)
{
html += String.Format("<td>{0}</td>", String.Empty);
}
else
{
int colSpan = 1;
int rowSpan = 1;
if ((nextCell != null) && (nextCell.Merge) && (currentCell.Merge)) {
colSpan = 2;
// Console.WriteLine(String.Format("{0} - {1}", currentCell.Address, nextCell.Address));
}
if ((nextCellBelow != null) && (nextCellBelow.Merge) && (currentCell.Merge)) {
Console.WriteLine(String.Format("{0} - {1}", currentCell.Address, nextCellBelow.Address));
}
html += String.Format("<td colspan={0} rowspan={1}>{2}</td>", colSpan, rowSpan, currentCell.Value);
}
}
html += String.Format("</tr>");
});
html += "</tbody></table>";
}//worksheet loop
}
}
return html;
}
As far as I can tell this is exactly what you need. What you were missing was the MergedCells property on the worksheet which lists all merged cells in the sheet.
My code handles row spans, column spans, and both at the same time. I did some testing with a spreadsheet that included both row, column and row/column spanning. In all cases they worked perfectly.
Code
int colSpan = 1;
int rowSpan = 1;
//check if this is the start of a merged cell
ExcelAddress cellAddress = new ExcelAddress(currentCell.Address);
var mCellsResult = (from c in worksheet.MergedCells
let addr = new ExcelAddress(c)
where cellAddress.Start.Row >= addr.Start.Row &&
cellAddress.End.Row <= addr.End.Row &&
cellAddress.Start.Column >= addr.Start.Column &&
cellAddress.End.Column <= addr.End.Column
select addr);
if (mCellsResult.Count() >0)
{
var mCells = mCellsResult.First();
//if the cell and the merged cell do not share a common start address then skip this cell as it's already been covered by a previous item
if (mCells.Start.Address != cellAddress.Start.Address)
continue;
if(mCells.Start.Column != mCells.End.Column) {
colSpan += mCells.End.Column - mCells.Start.Column;
}
if (mCells.Start.Row != mCells.End.Row)
{
rowSpan += mCells.End.Row - mCells.Start.Row;
}
}
//load up data
html += String.Format("<td colspan={0} rowspan={1}>{2}</td>", colSpan, rowSpan, currentCell.Value);
Related
Does someone know why it is inserting text instead of numbers? I defined it before inserting the value, I defined it when inserting the value. And nothing.
foreach (var result in Query.listResults)
{
var row = sheet.CreateRow(rowIndex);
var rowFiltr = result;
int split = rowFiltr.Split(';').Count(), i = 0;
foreach (var rowResult in rowFiltr.Split(';'))
{
if (i == 3 || i == 4)
{
row.CreateCell(i, CellType.Numeric).SetCellValue(rowResult);
}
else
{
row.CreateCell(i).SetCellValue(rowResult);
}
i++;
}
rowIndex++;
}
workbook.Write(fs);
Creating format style didn't help. Now the file is not creating.
if (i == 3 || i == 4)
{
var cell = row.CreateCell(i);
ICellStyle cellStyle = workbook.CreateCellStyle();
cellStyle.DataFormat = (i == 3) ? HSSFDataFormat.GetBuiltinFormat("0") : HSSFDataFormat.GetBuiltinFormat("0.00");
cell.SetCellType(CellType.Numeric);
cell.SetCellValue(rowResult);
}
else
{
row.CreateCell(i).SetCellValue(rowResult);
}
i++;
I have dynamically created array CheckBoxes and I want to do a proper validation if none of them are selected inside the Panel but if I keep on using for loops, the MessageBox keeps on appearing.
Can anyone help me find a way to do this better? I just want to check if a checkbox control is checked inside the Panel and if not, display a messagebox that will say "Select a Checkbox!" only ONCE.
Here is the code that I made for the dynamically created checkboxes in a panel:
for (int z = 0; z <= dataGridView.Columns.Count - 1; z++)
{
chk[z] = new CheckBox();
chk[z].Name = dataGridView.Columns[z].Name;
chk[z].Text = dataGridView.Columns[z].Name;
chk[z].AutoCheck = true;
chk[z].Bounds = new Rectangle(10, 20 + padding + dynamicHeight, 40, 22);
chk[z].Location = new Point(0, dynamicHeight);
chk[z].Size = new Size(120, 21);
panelCol.BackColor = Color.White;
//MessageBox.Show(chk[z].Name + "" + dataGridView.Columns[z].Name);
panelCol.Controls.Add(chk[z]);
//panelCol.AutoScrollMinSize = new Size(0, 100);
dynamicHeight += 20;
panelCol.Size = new Size(120, dynamicHeight);
}
Here is the code that I have came up:
btnValidate.MouseClick += (s, e) => //btnValidate Event
{
for (int z = 0; z < dataGridView.Columns.Count - 1; z++ )
{
if(chk[z].Checked == true)
{
ValidateCheck(dataGridView, chk);
}
else if(chk[z].Checked == false)
{
MessageBox.Show("Select a CheckBox!");
}
}
};
ValidateCheck method:
public static void ValidateCheck(DataGridView dataGridView, CheckBox[] chk)
{
FileStream fs = new FileStream(#"C:\brandon\InvalidColumnCheck.txt", FileMode.OpenOrCreate, FileAccess.Write);
StreamWriter sw = new StreamWriter(fs);
sw.BaseStream.Seek(0, SeekOrigin.End);
StringBuilder sb = new StringBuilder();
decimal num;
sw.WriteLine("----------------------------");
sw.WriteLine("");
for (int j = 0; j < dataGridView.ColumnCount - 1; j++)
{
if (chk[j].Checked == true && chk[j].Name.Contains(dataGridView.Columns[j].Name))
{
string column = chk[j].Name;
for (int k = 0; k < dataGridView.RowCount; k++)
{
if (!Decimal.TryParse(dataGridView.Rows[k].Cells[column].Value.ToString(), out num))
{
if (dataGridView.Rows[k].Cells[dataGridView.Columns[column].Name].Value.ToString() == null || dataGridView.Rows[k].Cells[dataGridView.Columns[column].Name].Value.ToString() == "" || dataGridView.Rows[k].Cells[dataGridView.Columns[column].Name].Value.ToString() == column)
{
}
else
{
//MessageBox.Show("COLUMN" + dataGridView.Columns[j].Name.ToString() + "" + dataGridView.Rows[k].Cells[column].Value.ToString() + " NOT A DECIMAL!");
sb.AppendLine("[Column " + chk[j].Name.ToString().ToUpper() + "] :" + dataGridView.Rows[k].Cells[column].Value.ToString() + " NOT A DECIMAL!");
}
}
}
sb.AppendLine("");
}
}
if (sb.ToString() == null || sb.ToString() == "" || sb.Length < dataGridView.Columns.Count)
{
sw.WriteLine("No Errors!");
sw.WriteLine("");
sw.WriteLine("----------------------------");
MessageBox.Show("No errors!");
Process.Start(#"C:\brandon\InvalidColumnCheck.txt");
}
else if (sb.ToString() != null || sb.ToString() != "")
{
sw.WriteLine(sb.ToString());
sw.WriteLine("----------------------------");
//MessageBox.Show(sb.ToString());
Process.Start(#"C:\brandon\InvalidColumnCheck.txt");
}
sw.Flush();
sw.Close();
}
Here another way to get all Checkboxes from Panel, which are checked (with Linq):
List<CheckBox> selectedItems = panelCol.Controls.OfType<CheckBox>().Where(chk => chk.Checked).ToList();
Please change the validation method like below,
public List<CheckBox> GetSelectedItems()
{
List<CheckBox> selectedList = new List<CheckBox>();
foreach(Control control in panelCol.Controls) // panelCol is your panel
{
if(control is CheckBox)
{
CheckBox chkCtrl = control as CheckBox;
if(chkCtrl.Checked)
{
selectedList.Add(chkCtrl);
}
}
}
return selectedList;
}
btnValidate.MouseClick += (s, e) =>//btnValidate Event
{
List<CheckBox> selectedItems = GetSelectedItems();
if(selectedItems.Count == 0)
MessageBox.Show("Select a CheckBox!");
else{
// Continue with other validation for the selected checkboxes from the list
}
}
Hope it helps!
I want to change the value of datagridview column according to the condition if the cell.value is minimum of the all values the cell must contain "L" , that is low. If the value is highest of all the cell.value = "H" rest should be medium. This is what i have done so far:
//Function to display interaction as High, Medium and Low
public void ShowInteraction(DataGridView dv, string columnName)
{
//MAX
var Max = dv.Rows.Cast<DataGridViewRow>()
.Max(r => Convert.ToDouble(r.Cells[columnName].Value));
//MIN
List<double> arr = new List<double>();
foreach (DataGridViewRow row in dv.Rows)
{
if (row != null && row.Cells[columnName].Value != null)
{
arr.Add(Convert.ToDouble(row.Cells[columnName].Value.ToString()));
}
}
double Min = arr.Min();
//show interaction in datagridview as H, M nad L
foreach (DataGridViewRow row in dv.Rows)
{
if (row.Cells[columnName].Value != null)
{
if ((double)row.Cells[columnName].Value == Min)
{
row.Cells[columnName].Value = Convert.ToString( "L" );
}
else if ((double)row.Cells[columnName].Value == Max)
{
row.Cells[columnName].Value = Convert.ToString( "H" );
}
else if ((double)row.Cells[columnName].Value < Max && (double)row.Cells[columnName].Value > Min)
{
row.Cells[columnName].Value = Convert.ToString("M");
}
else if ((double)row.Cells[columnName].Value == 0.0000)
{
row.Cells[columnName].Value = Convert.ToString("N");
}
else
{
row.Cells[columnName].Value = Convert.ToString("L");
}
}
}
}
And this the result:
My problem is that the medium and high values are showing but some times it escape the lowest value and do not show any "L" at some columns. The data generated is random data.
I cleaned up your code a little bit and I'm not seeing the error you are. Let me know if this helps you with your issue or not.
public partial class MainForm : Form {
public MainForm() {
InitializeComponent();
DataGridView dgv = new DataGridView();
dgv.Columns.Add("columnName", "columnName");
dgv.AllowUserToAddRows = false;
dgv.Rows.Add(21.0);
dgv.Rows.Add(15.0);
dgv.Rows.Add(20.0);
dgv.Rows.Add(25.0);
dgv.Rows.Add(30.12354122);
this.Controls.Add(dgv);
ShowInteraction(dgv, "columnName");
}
public void ShowInteraction(DataGridView dv, string columnName) {
List<double> values = dv.Rows.Cast<DataGridViewRow>().Select(row => Convert.ToDouble(row.Cells[columnName].Value)).ToList();
double Max = values.Max();
double Min = values.Min();
foreach (DataGridViewRow row in dv.Rows)
{
if (row.Cells[columnName].Value != null)
{
double cellValue = Convert.ToDouble(row.Cells[columnName].Value);
var cell = row.Cells[columnName];
if (cellValue == Min)
{
cell.Value = "L";
}
else if (cellValue == Max)
{
cell.Value = "H";
}
else if (cellValue < Max && cellValue > Min)
{
cell.Value = "M";
}
else if (cellValue == 0)
{
cell.Value = "N";
}
else
{
cell.Value = "L";
}
}
}
}
}
I am working on a project where I am creating a dynamic table.Now depending upon my condition I am adding link button to table cells,but the click event of my link button is not working.I am not getting why its not working,neither its showing any error.
Following is my code
public void makeCalendar()
{
tblcalendar.Rows.Clear();
//for current month
DateTime startingdate = StartDateOfMonth(DateTime.Now.AddMonths(monthclickedno));
DateTime enddate = EndDateOfMonth(DateTime.Now.AddMonths(monthclickedno));
string startingday = startingdate.DayOfWeek.ToString();
int startingdayno = Convert.ToInt32(startingdate.DayOfWeek);
string endday = enddate.DayOfWeek.ToString();//like saturday is 6,stating is from monday with 1 and ending is sunday with 7
int enddayno = Convert.ToInt32(enddate.DayOfWeek);
//for prevoius month
DateTime enddateprevious = (EndDateOfMonth(DateTime.Now.AddMonths(monthclickedno)));
//for next month
DateTime startingdatenext = StartDateOfMonth(DateTime.Now.AddMonths(1));
DateTime dtstart=startingdate.AddDays(-(startingdayno+1));
//sMonthName = "January";
//int iMonthNo = Convert.ToDateTime("01-" + sMonthName + "-2011").Month;
for (int i = 0; i <7;i++)
{
TableRow tr = new TableRow();
for (int j = 0; j < 7;j++ )
{
TableCell tc = new TableCell();
clickablecell ctCell = new clickablecell();
//tc.ID = idtc.ToString();
idtc++;
if(i==0)
{
tr.CssClass = "firstrow";
tc.CssClass = "firstrowcell";
if (j == 0)
tc.Text = "Sun";
else if (j == 1)
tc.Text = "Mon";
else if (j == 2)
tc.Text = "Tue";
else if (j == 3)
tc.Text = "Wed";
else if (j == 4)
tc.Text = "Thu";
else if (j == 5)
tc.Text = "Fri";
else if (j == 6)
tc.Text = "Sat";
tr.Cells.Add(tc);
}
else{
tc.CssClass = "othercells";
dtstart=dtstart.AddDays(1);
//if date is single digit like 1,2
if (dtstart.ToString("dd").Substring(0, (dtstart.ToString("dd").Length)-1) == "0")
ctCell.Text = (dtstart.ToString("dd").Substring(1));
else
ctCell.Text = (dtstart.ToString("dd"));
ctCell.Attributes.Add("onmouseover", "defColor=this.style.backgroundColor; this.style.backgroundColor='LightGray';");
ctCell.Attributes.Add("onmouseout", "this.style.backgroundColor=defColor;");
//ctCell.ID = k.ToString();
k++;
ctCell.Click += new clickablecell.ClickEventHandler(textcell_Click);
//check for events in this date
DataTable dtevents = checkEvents(dtstart.ToString("dd-MM-yyyy"));
if (dtevents.Rows.Count != 0)
{
LinkButton lnkevent = new LinkButton();
//lnkevent.ClientIDMode ="Static";
lnkevent.ID = (i+j).ToString();
if (dtevents.Rows.Count == 1)
{
if (dtevents.Rows[0]["eventtype"].ToString() == "Holiday")
{
lnkevent.Text = dtevents.Rows[0]["eventtype"].ToString();
lnkevent.CssClass = "tcholidaytext";
ctCell.CssClass = "tcholidaytext";
}
else if (dtevents.Rows[0]["eventtype"].ToString() == "Event")
{
lnkevent.Text = dtevents.Rows[0]["eventtype"].ToString();
lnkevent.CssClass = "tceventtext";
ctCell.CssClass = "tceventtext";
}
else
{
lnkevent.Text = dtevents.Rows[0]["eventtype"].ToString();
lnkevent.CssClass = "tcimpdaytext";
ctCell.CssClass = "tcimpdaytext";
}
}
else
{
ctCell.CssClass = "tcmixtext";
}
//lnkevent.Attributes.Add("onClick", "test();");
//lnkevent.Click += lnkevent_OnClick;
lnkevent.Click += new EventHandler(lnkevent_OnClick);
ctCell.Controls.Add(lnkevent);
}
tr.Cells.Add(ctCell);
}
tblcalendar.Rows.Add(tr);
}
}
}
public void lnkevent_OnClick(object sender,EventArgs e)
{
lblmonthname.Text = "hellooo";
txttitle.Text = "";
}
Sounds like you are adding a button, but not binding an event listener to it. I don't know much about how asp.net does event binding, but it sounds like that could be your problem.
Maybe this link can help?
asp.net dynamically button with event handler
int rowsCount = 0;
//This checks to see that both textbox for items and subitems do not gain focus at the same time
if (textBoxSubItems.Text != string.Empty)
txtItems.Enabled = false;
else
txtItems.Enabled = true;
if (comboBoxItems.SelectedItem != null)
{
int idx = dataGridViewTimesheet.Rows.Add();
DataGridViewRow row = dataGridViewTimesheet.Rows[idx];
row.Cells["items"].Value = comboBoxItems.SelectedItem.ToString() + "-" + textBoxSubItems.Text;
row.Cells["fromTime"].Value = DateTime.Now.ToLongTimeString();
row.Cells["toTime"].Value = null;
row.Cells["duration"].Value = null;
row.Cells["subTotal"].Value = null;
// row.Cells["comments"].Value = "1";
}
else
MessageBox.Show("Please select an item");
string strGetColumnValue;
if (dataGridViewTimesheet.Rows.Count != 0)
rowsCount = dataGridViewTimesheet.Rows.Count;
else
MessageBox.Show("No row in the datagridview");
while (dataGridViewTimesheet.Rows.Count > 0)
{
try
{
if (dataGridViewTimesheet.CurrentRow != null)
for (int counter = 0; counter < dataGridViewTimesheet.Columns.Count; counter++)
{
if (dataGridViewTimesheet.Columns[counter].Index == 3)
{
strGetColumnValue = dataGridViewTimesheet.Rows[rowsCount].Cells[counter].Value.ToString();
dataGridViewTimesheet.Rows[rowsCount - 1].Cells[3].Value = strGetColumnValue;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
Please i have 6 columns in a datagridview, the rows are added dynamically. What i want is when rows are more than one in the datagridview it should assign the value of the second column on the current(the last row created) row to the third column of the previous row. How do I achieve this.
Try this kind of thing
int count =1;
foreach (DataGridRow row in dataGridViewTimesheet.Rows)
{
if (count % 2 == 0)
{
string secondColumn = dataGridViewTimesheet.Rows[count -1].Cells[1].ToString();
dataGridViewTimesheet.Rows[count].Cells[2].Value = secondColumn;
}
count++;
}