I'm trying to design a table that has 3 additional tables in the last cell. Like this.
I've managed to get the first nested table into row 4, but my second nested table is going into cell(1,1) of the first table.
var wordApplication = new Word.Application();
wordApplication.Visible = true;
var wordDocument = wordApplication.Documents.Add();
var docRange = wordDocument.Range();
docRange.Tables.Add(docRange, 4, 1);
var mainTable = wordDocument.Tables[1];
mainTable.set_Style("Table Grid");
mainTable.Borders.Enable = 0;
mainTable.PreferredWidthType = Word.WdPreferredWidthType.wdPreferredWidthPercent;
mainTable.PreferredWidth = 100;
docRange.Collapse(Word.WdCollapseDirection.wdCollapseStart);
var phoneRange = mainTable.Cell(4, 1).Range;
phoneRange.Collapse(Word.WdCollapseDirection.wdCollapseStart);
phoneRange.Tables.Add(phoneRange, 3, 2);
var phoneTable = mainTable.Cell(4, 1).Tables[1];
phoneTable.set_Style("Table Grid");
phoneTable.Borders.Enable = 0;
phoneTable.AutoFitBehavior(Word.WdAutoFitBehavior.wdAutoFitContent);
phoneTable.Rows.RelativeHorizontalPosition = Word.WdRelativeHorizontalPosition.wdRelativeHorizontalPositionMargin;
phoneRange.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
I've tried collapsing the range, adding in a paragraph then collapsing the range again. No luck. I found this post and many similar ones, but I must be missing something.
Thanks for your time.
It usually helps in situation like these to add a line in your code: phoneRange.Select(); and having code execution end with that. Take a look at where the Range actually is. Now you can test using the keyboard where the Range needs to be in order to insert the next table successfully.
Since you say phoneRange selects outside the third row, rather than working with phoneRange try setting a new Range object to phoneTable.Range then collapse it to its end-point.
Related
I am using EPPlus to create a pivot table and now I am trying to use ConditionalFormatting on the pivot table and it simply doesn't seem to work. I have taken the example from Three color scale example and am trying to apply it on the cells of the pivot table:
var rng = worksheet.Cells["N3:Y88"]; // (This is the output of the pivot table)
var cfRule = rng.Worksheet.ConditionalFormatting.AddThreeColorScale(rng);
cfRule.LowValue.Color = ColorTranslator.FromHtml("#FF63BE7B");
cfRule.MiddleValue.Color = ColorTranslator.FromHtml("#FFFFEB84");
cfRule.MiddleValue.Type = eExcelConditionalFormattingValueObjectType.Percentile;
cfRule.MiddleValue.Value = 50;
cfRule.HighValue.Color = ColorTranslator.FromHtml("#FFF8696B");
And there isn't any impact. I use the same code on a set of regular cells and it works fine. Does EPPlus support the ability to conditionalformat a pivot table?
I found the answer in Pivot Table support in Conditional Formatting #399.
public static void AddConditionalFormattingToPivotTable(ExcelPivotTable pivotTable)
{
var worksheetXml = pivotTable.WorkSheet.WorksheetXml;
var element = worksheetXml.GetElementsByTagName("conditionalFormatting")[0];
((XmlElement)element).SetAttribute("pivot", "1");
}
I'm looking for a way to hide/disable certain categories on a chart. (The Y Axis)
How this works with the filter:
Does anyone know how i can accomplish this in .NET With Excel Interop?
I hope I'm not too late to the party because this question is already a month old by now, but better late than never, I guess.
If you know the index of the category, you can use this code:
// Replace ActiveChart with your specific chart.
// The index of the ChartGroup is always 1,
// unless you have multiple chart groups in your chart.
var chartGroup = (ChartGroup)_application.ActiveChart.ChartGroups(1);
var category = (ChartCategory)chartGroup.FullCategoryCollection(Index: 2);
category.IsFiltered = true;
If you only know the name, you need to iterate over the categories:
// If you want to skip the already hidden categories,
// you can use .CategoryCollection() instead of .FullCategoryCollection() two times.
var categories = (CategoryCollection)chartGroup.FullCategoryCollection();
for (int i = 1; i <= categories.Count; i++)
{
var category = (ChartCategory)chartGroup.FullCategoryCollection(i);
if (category.Name == "W2 - 13/01/17")
category.IsFiltered = true;
}
I'm using EPPlus in my project and I know you can copy an existing Shape however there is no method for copying an existing Chart.
I have setup a workbook with template charts that I need to duplicate and update the series to point to different datatable/sets.
I can populate the data no worries and can create new charts, but then need to size and position and style. It would be a lot easier to just clone the chart template and modify series and position to simplify the code.
Currently I use this approach:
// wb is an ExcelWorkbook
ExcelWorksheet ws = wb.Worksheets[sheetIdx];
ExcelChart chart = (ExcelChart)ws.Drawings[0];
ExcelChart cc = ws.Drawings.AddChart("Chart " + (i + 2), eChartType.ColumnClustered);
// invoke methods that will position and size new chart
// copy starting chart xml so will have identical styling, series, legend etc
var xml = XDocument.Parse(chart.ChartXml.InnerXml);
XNamespace nsC = "http://schemas.openxmlformats.org/drawingml/2006/chart";
XNamespace nsA = "http://schemas.openxmlformats.org/drawingml/2006/main";
// modify xml to update Category, Title and Values formulas
var fs = xml.Descendants(nsC + "f");
foreach (var f in fs)
{
f.Value = ws.Cells[f.Value].Offset(chartNumRows + 1, 0).FullAddressAbsolute;
}
// set new chart xml to modified xml.
cc.ChartXml.InnerXml = xml.ToString();
Which works, but there are several drawbacks.
1) The chart.series of the clone (cc in my example) has not been set, peeking at the code this is because it is only done during the object construction. If I could get this property to update then I would be able to easily resolve the second issue
2) I need to remove all series and add new ones and because the series property isn't initialised properly this is harder than it should be.
Any help getting properties to initialise in the chart or a better method of cloning the original would be greatly appreciated!
It seems like there is no built-in functionality for this and an other reload methods I could come up with required too many changes to the EPPlus source, so until I find a better solution I have added the following method to EPPlus\Drawings\ExcelDrawings.cs
public ExcelChart CloneChart(ExcelChart SourceChart, String NewName)
{
// Create clone
var tempClone = this.AddChart(NewName, SourceChart.ChartType, null);
tempClone.ChartXml.InnerXml = SourceChart.ChartXml.InnerXml;
// Reload clone
using (tempClone.Part.Stream = new MemoryStream())
{
// Create chart object using temps package and xml
var chartXmlBytes = Encoding.ASCII.GetBytes(tempClone.ChartXml.OuterXml);
tempClone.Part.Stream.Write(chartXmlBytes, 0, chartXmlBytes.Length);
var finalClone = ExcelChart.GetChart(this, tempClone.TopNode);
// Remove old from collection
var index = _drawingNames[tempClone.Name];
var draw = _drawings[index];
for (int i = index + 1; i < _drawings.Count; i++)
_drawingNames[_drawings[i].Name]--;
_drawingNames.Remove(draw.Name);
_drawings.Remove(draw);
// Add new to collection
finalClone.Name = tempClone.Name;
_drawings.Add(finalClone);
_drawingNames.Add(finalClone.Name, _drawings.Count - 1);
// Done
return finalClone;
}
}
I am attempting to add consecutive tables to a word document from C# (VS 2013, .NET 4.5, Word 2013) and the code below nests the second table within the first table. I am attempting to create distinct tables that are added between two bookmarks (I have tested this with a range defined as a single bookmark as well).
I own the code and source/destination document format, so i can make any changes as needed, however the content is all dynamic, so i can't just create a pre-defined bookmark for each table.
A subset of the code that is representative of my issue is below:
Word.Application oWord = new Word.Application();
Word.Document oTargetWordDoc = oWord.Documents.Open(WordTemplateFile);
object oBookmarkBegin = "bookmark_begin";
object oBookmarkEnd = "bookmark_end";
object oBookmarkBeginRange = oTargetWordDoc.Bookmarks.get_Item(ref oBookmarkBegin).Range.Start;
object oBookmarkEndRange = oTargetWordDoc.Bookmarks.get_Item(ref oBookmarkEnd).Range.Start;
Word.Range oRange = oTargetWordDoc.Range(ref oBookmarkBeginRange, ref oBookmarkEndRange);
Word.Table wtTable1 = oRange.Tables.Add(oRange, 1, 2);
wtTable1.Cell(1,1).Range.Text = "t1/r1/c1";
wtTable1.Cell(1,2).Range.Text = "t1/r1/c2";
Word.Table wtTable2 = oRange.Tables.Add(oRange, 1, 2);
wtTable2.Cell(1,1).Range.Text = "t2/r1/c1";
wtTable2.Cell(1,2).Range.Text = "t2/r1/c2";
// other goo/close/save/etc...
The output looks like this:
t1/r1/c1|t1/r1/c2
t2/r1/c1|t2/r1/c2| <-- nested within column 1 in the first table (notice trailing | marking beginning of first table's second column)
Clearly, I would like two distinct tables, without nesting, resulting in:
t1/r1/c1|t1/r1/c2
t2/r1/c1|t2/r1/c2
Any assistance in this matter would be greatly appreciated!
I know what index out of bounds is all about. When I debug I see why as well. basically what is happening is I do a filter on my database to look for records that are potential/pending. I then gather a array of those numbers send them off to another server to check to see if those numbers have been upgraded to a sale. If it has been upgraded to a sale the server responds back with the new Sales Order ID and my old Pending Sales Order ID (SourceID). I then do a for loop on that list to filter it down that specific SourceID and update the SourceID to be the Sales Order ID and change a couple of other values. Problem is is that when I use that filter on the very first one it throws a index out of bounds error. I check the results returned by the filter and it says 0. Which i find kind of strange because I took the sales order number from the list so it should be there. So i dont know what the deal is. Here is the code in question that throws the error. And it doesn't do it all the time. Like I just ran the code this morning and it didn't throw the error. But last night it did before I went home.
filter.RowFilter = string.Format("Stage = '{0}'", Potential.PotentialSale);
if (filter.Count > 0)
{
var Soids = new int[filter.Count];
Console.Write("Searching for Soids - (");
for (int i = 0; i < filter.Count; i++)
{
Console.Write(filter[i][1].ToString() + ",");
Soids[i] = (int)filter[i][1];
}
Console.WriteLine(")");
var pendingRecords = Server.GetSoldRecords(Soids);
var updateRecords = new NameValueCollection();
for (int i = 0; i < pendingRecords.Length; i++)
{
filter.RowFilter = "Soid = " + pendingRecords[i][1];
filter[0].Row["Soid"] = pendingRecords[i][0];
filter[0].Row["SourceId"] = pendingRecords[i][1];
filter[0].Row["Stage"] = Potential.ClosedWon;
var potentialXML = Potential.GetUpdatePotentialXML(filter[0].Row["Soid"].ToString(), filter[0].Row["Stage"].ToString());
updateRecords.Add(filter[0].Row["ZohoID"].ToString(), potentialXML);
}
if i'm counting right line 17 is the error where the error is thrown. pendingRecords is a object[][] array. pendingRecords[i] is the individual records. pendingRecords[i][0] is the new Sales OrderID (SOID) and pendingRecords[i][1] is the old SOID (now the SourceID)
Any help on this one? is it because i'm changing the SOID to the new SOID, and the filter auto updates itself? I just don't know
Well I ended up changing how it worked all together and it actually sorts it a bit nicer now. The code i am about to post has a bunch of hard coded numbers due to the structure of my table that is returned. Sorry about that. I have learned since then to not do that, but i am working on a different project now and will change that when I have to change the program. But here is the solution.
var potentials = Server.GetNewPotentials(); //loads all records from server
for (int i = 0; i < potentials.Length; i++)
{
var filter = AllPotentials.DefaultView;
var result1 = CheckSoidOrSource(potentials[i].Soid, true);
var result2 = CheckSoidOrSource(potentials[i].SourceID,false) ;
//This potential can't be found at all so let's add it to our table
if (result1+result2==0)
{
Logger.WriteLine("Found new record. Adding it to DataTable and sending it to Zoho");
AllPotentials.Add(potentials[i]);
filter.RowFilter = string.Format("Soid = '{0}'", potentials[i].SourceID);
var index = AllPotentials.Rows.IndexOf(filter[0].Row);
ZohoPoster posterInsert = new ZohoPoster(Zoho.Fields.Potentials, Zoho.Calls.insertRecords);
AllPotentials.Rows[index]["ZohoID"] = posterInsert.PostNewPotentialRecord(3, filter[0].Row);
}
//This potential is not found, but has a SourceId that matches a Soid of another record.
if (result1==0 && result2 == 1)
{
Logger.WriteLine("Found a record that needs to be updated on Zoho");
ZohoPoster posterUpdate = new ZohoPoster(Zoho.Fields.Potentials, Zoho.Calls.updateRecords);
filter.RowFilter = string.Format("Soid = '{0}'", potentials[i].SourceID);
var index = AllPotentials.Rows.IndexOf(filter[0].Row);
AllPotentials.Rows[index]["Soid"] = potentials[i].Soid;
AllPotentials.Rows[index]["SourceId"] = potentials[i].SourceID;
AllPotentials.Rows[index]["PotentialStage"] = potentials[i].PotentialStage;
AllPotentials.Rows[index]["UpdateRecord"] = true;
AllPotentials.Rows[index]["Amount"] = potentials[i].Amount;
AllPotentials.Rows[index]["ZohoID"] = posterUpdate.UpdatePotentialRecord(3, filter[0].Row);
}
}
AllPotentials.AcceptChanges();
}
private int CheckSoidOrSource(string Soid, bool checkSource)
{
var filter = AllPotentials.DefaultView;
if (checkSource)
filter.RowFilter = string.Format("Soid = '{0}' OR SourceId = '{1}'",Soid, Soid);
else
filter.RowFilter = string.Format("Soid = '{0}'", Soid);
return filter.Count;
}
basically what is happening is that i noticed something about my data when I filter it this way. The two results would only return the following results (0,0) (0,1) and (1,0) (0,0) means that the record doesn't exist at all in this table so I need to add it. (1,0) means that the Sales Order ID (Soid) matches another Soid in the table so it already exists. Lastly (0,1) means that the Soid doesn't exist in this table but i found a record that has the Soid as it's source...which to me means that the one that had it as a source has been upgraded from a potential to a sale, which in turn means i have to update the record and Zoho. This worked out to much less work for me because now I don't have to search for won and lost records, i only have to search for lost records. less code same results is always a good thing :)