I am trying to create an advanced filter in Excel from C# to copy unique data from one sheet to another, at least I get it in Excel and if I use Interop like this :
Excel.Range rang = sheet2.get_Range("A2");
Excel.Range oRng = sheet.get_Range("I2", "I" + (lst.Count + 1));
oRng.AdvancedFilter(Excel.XlFilterAction.xlFilterCopy, CriteriaRange: Type.Missing,
CopyToRange: rang, Unique: true);
Works fine but I'm doing all my application with EPPlus and it will be better if I can do the same without Interop.
So, it is possible?
Since Advanced Filter is an excel function you need the full Excel DOM to access it. Epplus doesnt have that - it just generated the XML to feed to excel which will then apply its "interpretation", so to speak.
But since you have the power of .NET at your disposal, you can use linq fairly easily to do the same thing by querying the cell store and using .distinct() to get the unique list. The only wrinkle is you have to create your own IEquitableComparer. This will do it:
[TestMethod]
public void Distinct_Filter_Test()
{
//Throw in some data
var datatable = new DataTable("tblData");
datatable.Columns.AddRange(new[]
{
new DataColumn("Col1", typeof (int)), new DataColumn("Col2", typeof (string))
});
var rnd = new Random();
for (var i = 0; i < 10; i++)
{
var row = datatable.NewRow();
row[0] = rnd.Next(1, 3) ;row[1] = i%2 == 0 ? "even": "odd";
datatable.Rows.Add(row);
}
//Create a test file
var fi = new FileInfo(#"c:\temp\Distinct_Filter.xlsx");
if (fi.Exists)
fi.Delete();
using (var pck = new ExcelPackage(fi))
{
//Load the random data
var workbook = pck.Workbook;
var worksheet = workbook.Worksheets.Add("data");
worksheet.Cells.LoadFromDataTable(datatable, true);
//Cells only contains references to cells with actual data
var rows = worksheet.Cells
.GroupBy(cell => cell.Start.Row)
.Skip(1) //Exclude header
.Select(cg => cg.Select(c => c.Value).ToArray())
.Distinct(new ArrayComparer())
.ToArray();
//Copy the data to the new sheet
var worksheet2 = workbook.Worksheets.Add("Distinct");
worksheet2.Cells.LoadFromArrays(rows);
pck.Save();
}
}
public class ArrayComparer: IEqualityComparer<object[]>
{
public bool Equals(object[] x, object[] y)
{
return !x.Where((o, i) => !o.Equals(y[i])).Any();
}
public int GetHashCode(object[] obj)
{
//Based on Jon Skeet Stack Overflow Post
unchecked
{
return obj.Aggregate((int) 2166136261, (acc, next) => acc*16777619 ^ next.GetHashCode());
}
}
}
Related
Using the unwieldy and ponderous but full-featured Excel Interop, background error checking can be toggled off like so:
Excel.Application excelApp = new Excel.Application();
excelApp.ErrorCheckingOptions.BackgroundChecking = false;
...as shown here
I am getting the green triangles indicating a bad number like so:
...which I want to turn off. These are just string vals that should not be flagged as bad or suspicious.
So how can I turn background error checking off for the Excel app object, or otherwise programmatically prevent these green triangles, using EPPlus?
UPDATE
Changing the code from this:
using (var custNumCell = priceComplianceWorksheet.Cells[rowToPopulate, DETAIL_CUSTNUM_COL])
{
custNumCell.Style.Font.Size = DATA_FONT_SIZE;
custNumCell.Value = _custNumber;
custNumCell.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
}
...to this:
using (var custNumCell = priceComplianceWorksheet.Cells[rowToPopulate, DETAIL_CUSTNUM_COL])
{
custNumCell.Style.Font.Size = DATA_FONT_SIZE;
custNumCell.ConvertValueToAppropriateTypeAndAssign(_custNumber);
custNumCell.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
}
// Adapted from https://stackoverflow.com/questions/26483496/is-it-possible-to-ignore-excel-warnings-when-generating-spreadsheets-using-epplu
public static void ConvertValueToAppropriateTypeAndAssign(this ExcelRangeBase range, object value)
{
string strVal = value.ToString();
if (!String.IsNullOrEmpty(strVal))
{
decimal decVal;
double dVal;
int iVal;
if (decimal.TryParse(strVal, out decVal))
{
range.Value = decVal;
}
else if (double.TryParse(strVal, out dVal))
{
range.Value = dVal;
}
else if (Int32.TryParse(strVal, out iVal))
{
range.Value = iVal;
}
else
{
range.Value = strVal;
}
}
else
{
range.Value = null;
}
}
...semi-fixed it; it is now:
But notice that the leading "0" got stripped out. I need that to remain, so this is still only half-solved.
UPDATE 2
I tried the suggestion from the comment below that pointed here, and added this code:
//Create the import nodes (note the plural vs singular
var ignoredErrors =
priceComplianceWorksheet.CreateNode(XmlNodeType.Element, "ignoredErrors",
xdoc.DocumentElement.NamespaceURI);
var ignoredError
priceComplianceWorksheet.CreateNode(XmlNodeType.Element, "ignoredError",
xdoc.DocumentElement.NamespaceURI);
ignoredErrors.AppendChild(ignoredError);
//Attributes for the INNER node
var sqrefAtt = priceComplianceWorksheet.CreateAttribute("sqref");
sqrefAtt.Value = range;
var flagAtt =
priceComplianceWorksheet.CreateAttribute("numberStoredAsText");
flagAtt.Value = "1";
ignoredError.Attributes.Append(sqrefAtt);
ignoredError.Attributes.Append(flagAtt);
//Now put the OUTER node into the worksheet xml
priceComplianceWorksheet.LastChild.AppendChild(ignoredErrors);
...but "CreateAttribute" and "LastChild" are not recognized...?!?
In response to update 2, you just need to reference the XmlDocument and use that to generate the XML:
var xdoc = priceComplianceWorksheet.WorksheetXml;
//Create the import nodes (note the plural vs singular
var ignoredErrors = xdoc.CreateNode(XmlNodeType.Element, "ignoredErrors",xdoc.DocumentElement.NamespaceURI);
var ignoredError = xdoc.CreateNode(XmlNodeType.Element, "ignoredError",xdoc.DocumentElement.NamespaceURI);
ignoredErrors.AppendChild(ignoredError);
//Attributes for the INNER node
var sqrefAtt = xdoc.CreateAttribute("sqref");
sqrefAtt.Value = "C2:C10"; // Or whatever range is needed....
var flagAtt = xdoc.CreateAttribute("numberStoredAsText");
flagAtt.Value = "1";
ignoredError.Attributes.Append(sqrefAtt);
ignoredError.Attributes.Append(flagAtt);
//Now put the OUTER node into the worksheet xml
xdoc.LastChild.AppendChild(ignoredErrors);
I have a primary Entity (Self-Insurance) and a secondary entity (Compensation). They have a 1:N relationship. So in my main form of Self Insurance I have a sub-grid with the name 'Worker_Compensation' where i am adding up some payroll values.
I have 2 questions. . .
1: The thing I want is that when I add some values in the sub-grid. I need to show a sum of all payrolls in the text below of my main form named as 'TOTAL'.
2: Where should i call this java script(On which event) Onload or Onsave of form ? or else where because I can seems to locate the events on Subgrid.
I am using a java script for this purpose.
enter code here
function setupGridRefresh() {
var targetgrid = document.getElementById("Worker_Compensation");
// If already loaded
if (targetgrid.readyState == 'complete') {
targetgrid.attachEvent("onrefresh", subGridOnload);
}
else {
targetgrid.onreadystatechange = function applyRefreshEvent() {
var targetgrid = document.getElementById("Worker_Compensation");
if (targetgrid.readyState == 'complete') {
targetgrid.attachEvent("onrefresh", subGridOnload);
}
}
}
subGridOnload();
}
function subGridOnload() {
//debugger;
var grid = Xrm.Page.ui.controls.get('Worker_Compensation')._control;
var sum = 0.00;
if (grid.get_innerControl() == null) {
setTimeout(subGridOnload, 1000);
return;
}
else if (grid.get_innerControl()._element.innerText.search("Loading") != -1) {
setTimeout(subGridOnload, 1000);
return;
}
var ids = grid.get_innerControl().get_allRecordIds();
var cellValue;
for (i = 0; i < ids.length; i++) {
if (grid.get_innerControl().getCellValue('new_estannualpayroll', ids[i]) != "") {
cellValue = grid.get_innerControl().getCellValue('new_estannualpayroll', ids[i]);
cellValue = cellValue.substring(2);
cellValue = parseFloat(cellValue);
sum = sum + cellValue;
}
}
var currentSum = Xrm.Page.getAttribute('new_payrolltotal').getValue();
if (sum > 0 || (currentSum != sum && currentSum != null)) {
Xrm.Page.getAttribute('new_payrolltotal').setValue(sum);
}
}
This piece of code is not working. after i add values in the grid my textbox remains empty!
Thanks in advance
If you are upgrading to Microsoft CRM 2015 soon or are already on Microsoft CRM 2015, you can do this without any JavaScript by simply creating a new calculated rollup field and placing that underneath the sub grid, or wherever you wish to place it on the form. Note that this field is calculated ever 12 hours, but if you wish to, it could be calculated on form load via JavaScript. You can see details about that at https://msdn.microsoft.com/en-us/library/dn817863.aspx -"Calculated and Rollup Attributes". The TechNet document, "Define rollup fields" at https://technet.microsoft.com/library/dn832162.aspx has some good examples, scenarios, and discussion about the limitations of the rollup fields.
You can do it with subgrid's onRefresh. This is also unsupportted way but it works. You must add this functions to your javascript
function AddEventToGridRefresh(gridName, functionToCall) {
// retrieve the subgrid
var grid = document.getElementById(gridName);
// if the subgrid still not available we try again after 1 second
if (grid == null) {
setTimeout(function () {AddEventToGridRefresh(gridName, functionToCall);}, 1000);
return;
}
// add the function to the onRefresh event
grid.control.add_onRefresh(functionToCall);
}
// function used in this example
function AdviseUser() {
alert("Sub-Grid refreshed");
}
For more information, here is the link
I have a method that adds a new item to an EF table, then queries back the table to return a subset of the table. It needs to return to the caller a set of "rows", each of which is a set of columns. I'm not sure how to do this. I have some code, but I think it's wrong. I don't want to return ONE row, I want to return zero or more rows. I'm not sure what DataType to use... [qryCurrentTSApproval is an EF object, referring to a small view in SS. tblTimesheetEventlog is also an EF object, referring to the underlying table]
Ideas?
private qryCurrentTSApproval LogApprovalEvents(int TSID, int EventType)
{
using (CPASEntities ctx = new CPASEntities())
{
tblTimesheetEventLog el = new tblTimesheetEventLog();
el.TSID = TSID;
el.TSEventType = EventType;
el.TSEUserName = (string)Session["strShortUserName"];
el.TSEventDateTime = DateTime.Now;
ctx.tblTimesheetEventLogs.AddObject(el);
ctx.AcceptAllChanges();
var e = (from x in ctx.qryCurrentTSApprovals
where x.TSID == TSID
select x);
return (qryCurrentTSApproval)e;
}
}
Change your method return type to a collection of qryCurrentTSApproval
private List<qryCurrentTSApproval> LogApprovalEvents(int TSID, int EventType)
{
using (CPASEntities ctx = new CPASEntities())
{
// some other existing code here
var itemList = (from x in ctx.qryCurrentTSApprovals
where x.TSID == TSID
select x).ToList();
return itemList;
}
}
I am currently developing a testing framework for a web data entry application that is using the Telerik ASP.Net framework and have run into a blocker. If I step through my code in debug mode the test will find the text box that I am looking for and enter some test data and then save that data to the database. The problem that I am running into is that when I let the test run on it's own the test fails saying that it couldn't fine the column that was declared. Here is my code:
/*Method to enter test data into cell*/
private TableCell EditFieldCell(string columnHeader)
{
var columnIndex = ColumnIndex(columnHeader);
if (columnIndex == -1)
throw new InvalidOperationException(String.Format("Column {0} not found.", columnHeader));
return NewRecordRow.TableCells[columnIndex];
}
/*Method to return column index of column searching for*/
public int ColumnIndex(string columnHeader)
{
var rgTable = GridTable;
var rgCount = 0;
var rgIndex = -1;
foreach (var rgRow in rgTable.TableRows)
{
foreach (var rgElement in rgRow.Elements)
{
if (rgElement.Text != null)
{
if (rgElement.Text.Equals(columnHeader))
{
rgIndex = rgCount;
break;
}
}
rgCount++;
}
return rgIndex;
}
My thinking is that something with my nested for loops is presenting the problem because the rgIndex value that is returned when I let the program run is -1 which tells me that the code in the for loops isn't being run.
TIA,
Bill Youngman
Code that gets the table Column index. You need to pass the Table(verify that the table exists while debug):
public int GetColumnIndex(Table table, string headerName)
{
ElementCollection headerElements = table.TableRows[0].Elements; //First row contains the header
int counter = 0;
foreach (var header in headerElements)
{
if (header.ClassName != null && header.ClassName.Contains(headerName)) //In this case i use class name of the header you can use the text
{
return counter;
}
counter++;
}
// If not found
return -1;
}
I have 4 reports Report A, Report B, Report C and Report D with datasources dsA, dsB, dsC and dsD respectively.
Report A is a Main Report which has the subreport B has a subreport C ...
The Report A fills the datasource dsB in the SubreportProcessingEvent with the parameter from ReportA.
i would need an event which is fired for every row in Report B so that I pass parameter from Report B and fill the Report C and C parameter to Report D....
code in SubreportProcessingEventArg
SearchValue = new SqlParameter[2];
SqlConnection thisConnection = new SqlConnection(thisConnectionString);
DataSet thisDataSet = new DataSet();
SearchValue[0] = new SqlParameter("#TPlanId", e.Parameters[1].Values[0]);
SearchValue[1] = new SqlParameter("#ProblemId", e.Parameters[0].Values[0]);
thisDataSet = SqlHelper.ExecuteDataset(thisConnection, "Proc_TP_Goal", SearchValue);
/* Associate thisDataSet (now loaded with the stored procedure result) with the ReportViewer datasource */
ReportDataSource datasource = new ReportDataSource("Goal_Proc_TP_Goal", thisDataSet.Tables[0]);
e.DataSources.Add(datasource);
i was not able to figure out the 3rd and 4th level of event handler any suggestion or examples would be greatly appreciated.
Thanks
I do this, I have a parameter in the sub-subreports that pass the row of the subreport. I hope you understand, if not let me know and I will post a sourcecode.
if ("RendicionDetalleCodigosReporte".Equals(e.ReportPath))
{
if (data != null)
{
RendicionDetalleData detalle = new RendicionDetalleData();
detalle.row = 0;
int row = Convert.ToInt32(e.Parameters[0].Values[0]);
foreach (var det in data.Detalles)
{
if (det.row.Equals(row))
{
detalle = det;
break;
}
}
if (detalle.row == 0)
{
e.DataSources.Add(new ReportDataSource("RendicionDetalleCodigo", new List<RendicionDetalleCodigosData>()));
}
else
{
e.DataSources.Add(new ReportDataSource("RendicionDetalleCodigo", detalle.Codigos));
}
}
else
{
e.DataSources.Add(new ReportDataSource("RendicionDetalleCodigo", new List<RendicionDetalleCodigosData>()));
}
}
else
{
if (data != null)
{
e.DataSources.Add(new ReportDataSource("RendicionDetalle", data.Detalles));
}
else
{
e.DataSources.Add(new ReportDataSource("RendicionDetalle", new List<RendicionDetalleData>()));
}
}