I have this code that works fine, adding a FreezePane to row 7, col 3 of the first sheet (counting from the left):
private void FreezePane(int rowNum, int colNum)
{
Range cellToFreeze = (Range)_xlSheet.Cells[rowNum, colNum];
cellToFreeze.Activate();
cellToFreeze.Application.ActiveWindow.FreezePanes = true;
}
After adding another couple of sheets (the second sheet contains data as a source for a pivot table, and the third sheet contains the pivot table), I want to add a freezepane in the same place of the third/pivot Table sheet, and so I tried this:
private void FreezePanePivotTable(int rowToFreeze, int colToFreeze)
{
Range pivotTableCellToFreeze = (Range)_xlPivotTableSheet.Cells[rowToFreeze, colToFreeze];
pivotTableCellToFreeze.Activate();
pivotTableCellToFreeze.Application.ActiveWindow.FreezePanes = true;
}
That, though, crashed with "Activate method of Range class failed Exception Source: Microsoft Office Excel
Exception StackTrace: at System.RuntimeType.ForwardCallToInvokeMember(..."
So I thought, "maybe you can only have one frozen pane in a workbook" and tried calling only the new method (leaving the first sheet unfrozen), and I get this seemingly bizarre err msg: "Unable to set the Size property of the Font class"
Where this exception is occurring is in the second sheet - the source data for sheet 3/PivotTable sheet! Why does setting the font size all of a sudden cause a problem? Line 3418 is the last line below:
var itemCodeLabelCell = _xlPivotDataSheet.Cells[1, 1];
itemCodeLabelCell.Value2 = "ItemCode";
itemCodeLabelCell.Style.WrapText = false;
itemCodeLabelCell.Style.Font.Size = 12;
Do different rules apply to adding freezepanes on the main (first) sheet and others, or is it that FreezePanes and PivotTables can't coexist, or what?
SomeSheet.SomeRange.Activate() fails if SomeSheet is not already active, hence probably the "Activate method of Range class failed".
You must activate the sheet before:
_xlPivotTableSheet.Activate();
pivotTableCellToFreeze.Activate();
Related
I am creating charts in PowerPoint. The below code opens two excel applications. One opens in the background that is invisible. The second one opens after the method ends. I need to make sure second excel either never open ideally or I can close it after it opens.
I have tried the below things but none worked.
I have tried forcing GC, Manual ReleaseComObject, Killing Excel process
I have tried separating excel COM objects and forcing GC
private void BtnInsert_Click(object sender, EventArgs e)
{
var Addin = Globals.ThisAddIn;
Microsoft.Office.Interop.PowerPoint.Application activeApplication = Addin.Application;
DocumentWindow activeWindows = activeApplication.ActiveWindow;
Microsoft.Office.Interop.PowerPoint.View activeView = activeWindows.View;
Slide activeSlide = activeView.Slide;
Microsoft.Office.Interop.PowerPoint.Shapes slideShape = activeSlide.Shapes;
Microsoft.Office.Interop.PowerPoint.Shape shape = slideShape.AddChart2(-1, XlChartType.xl3DBarClustered, -1, -1, -1, -1, true);
Microsoft.Office.Interop.PowerPoint.Chart chart = shape.Chart;
//Access the chart data
Microsoft.Office.Interop.PowerPoint.ChartData chartData = chart.ChartData;
chartData.Activate();
//Create instance to Excel workbook to work with chart data
Workbook workbook = chartData.Workbook;
Microsoft.Office.Interop.Excel.Application workbookApplication = workbook.Application;
workbookApplication.Visible = false;
workbookApplication.WindowState = XlWindowState.xlMinimized;
//Accessing the data worksheet for chart
Worksheet worksheet = workbook.Worksheets[1];
// I am adding data here
// This is not required to reproduce this
chartData.BreakLink();
workbook.Close(true);
}
Also, note that this issue does not occur while updating data.
Remove chartData.Activate() and chartData.BreakLink() solves this.
Although online documentation says that chartdata.activate is required before accessing the workbook.
Otherwise, we will get a null reference.
I think the documentation is incorrect or it does not apply to vsto.
When programmatically generating a "normal" Excel sheet, a Range's PageBreak property can be set to xlPageBreakManual and then you can specify where the page is to break with code like so:
workbook.Worksheets[0].VPageBreaks.Add(sheet.Range["G1"]);
...but is this possible when printing a PivotTable? I would think not, since the PivotTable is fed from a datasource (a page of "raw data" in my case, which I subsequently hide), but if it is, I would like to know how.
This is what I do now to prepare the PivotTablized sheet for printing:
private void FormatPivotTable()
{
. . .
ConfigureForPrinting(_xlPivotTableSheet.UsedRange.Rows.Count);
}
private void ConfigureForPrinting(int finalRow)
{
string lastColumn = GetExcelTextColumnName(_xlPivotTableSheet.UsedRange.Columns.Count);
string printArea = String.Format("A1:{0}{1}", lastColumn, finalRow);
_xlPivotTableSheet.PageSetup.PrintArea = printArea;
_xlPivotTableSheet.PageSetup.Orientation = XlPageOrientation.xlLandscape;
_xlPivotTableSheet.PageSetup.Zoom = false;
_xlPivotTableSheet.PageSetup.FitToPagesWide = 1;
_xlPivotTableSheet.PageSetup.FitToPagesTall = 50;
_xlPivotTableSheet.PageSetup.LeftMargin = _xlApp.Application.InchesToPoints(0.5);
_xlPivotTableSheet.PageSetup.RightMargin = _xlApp.Application.InchesToPoints(0.5);
_xlPivotTableSheet.PageSetup.TopMargin = _xlApp.Application.InchesToPoints(0.5);
_xlPivotTableSheet.PageSetup.BottomMargin = _xlApp.Application.InchesToPoints(0.5);
_xlPivotTableSheet.PageSetup.HeaderMargin = _xlApp.Application.InchesToPoints(0.5);
_xlPivotTableSheet.PageSetup.FooterMargin = _xlApp.Application.InchesToPoints(0.5);
string rangeToRepeat = string.Format("$A$6:${0}$7", lastColumn);
_xlPivotTableSheet.PageSetup.PrintTitleRows = rangeToRepeat;
}
What the user wants is that each logical block of data be kept together on a sheet, so that a block (5 rows) does not span more than one sheet. IOW, if there are less than 5 rows of space on the current page when a block begins, skip to the next page with a page break.
Is this possible with PivotTables?
I understand the that when you are referring to "logical blocks of data" it means data corresponding to a PivotFieldItem. If that is the case the try this:
Set these PivotTable properties to TRUE:
ActiveSheet.PivotTables("PivotTable1").PrintTitles = True
ActiveSheet.PivotTables("PivotTable1").RepeatItemsOnEachPrintedPage = True
Also set to TRUE the LayoutPageBreak property of the PivotField for which you want to set a page break every time there is a change of item:
ActiveSheet.PivotTables("PivotTable1").PivotFields("Class").LayoutPageBreak = True
Also need to set this property to TRUE in case there are items with only one record.
ActiveSheet.PivotTables("PivotTable1").PivotFields("Class").LayoutBlankLine = True
Replace "PivotTable1" and "Class" values as required
Although the commands above are in VBA they should give you a good idea of how to set these properties using C#
I want to add one picture (displaying "DRAFT") by printable Excel worksheet in C# EPPlus.
I need to know if there is a way to find the last visible row of each page of a worksheet when you are printing it. I can't pretend that it will always be a fix number of row per page because it depends on the content of the cells.
Here is my current code that use a fix number of row per page (30) to insert image. This result in approximately one image per printable page except that in each new page the image is not at the same place. (Slightly off, depending on content of cells.)
public void InsertDraftImage(ExcelWorksheet worksheet, FileInfo draft_image)
{
int maxRowNumber = worksheet.Dimension.End.Row;
int rowByPage = 30;
int numberOfPage = (maxRowNumber / rowByPage) + 1;
ExcelPicture picture = null;
for(int i = 0; i < numberOfPage; i++)
{
if(draft_image != null)
{
picture = worksheet.Drawings.AddPicture(i.ToString(), draft_image);
picture.SetSize(609, 545); //original image size
picture.SetPosition(i * rowByPage, 0, 1, 0);
picture.EditAs = eEditAs.Absolute;
}
}
After trying to implement the missing code in 'ExcelHeaderFooter.cs' from the EPPlus with a workmate without success, we finally did it by following Ernie suggestion!!
There is my final code to insert a picture into each page of a printable excel file generate with EPPlus in C#.
It is done by adding the picture in the footer and setting the Boolean ScaleWithDoc to false (default = true).
public void InsertDraftImage(ExcelWorksheet worksheet, FileInfo draft_image)
{
ExcelHeaderFooterText footer = worksheet.HeaderFooter.OddFooter; //all page have same footer
footer.InsertPicture(draft_image, PictureAlignment.Centered);
}
Added this code in my method to create the ExcelWorksheet (all the other excel style, populate, settings).
XmlAttribute temp = worksheet.WorksheetXml.CreateAttribute("scaleWithDoc");
temp.Value = "0";
worksheet.WorksheetXml.GetElementsByTagName("headerFooter")[0].Attributes.Append(temp);
package.Save();
This is what happens:
xlValues is set as an Excel.Range object.
I have tried the following as well, all giving me the same error:
//xlValueRange = xlSheet...
.get_Range("A1:A5,A15:A25,A50:A65");
.UsedRange.Range["A1:A5,A15:A25,A50:A65"];
.Range["A1:A5,A15:A25,A50:A65"];
xlApp.ActiveWorkbook.ActiveSheet.Range["A1:A5,A15:A25,A50:A65"];
//I have also tried these alternatives with ".Select()" after the brackets and
//", Type.Missing" inside the brackets
//This works though...
xlSheet.Range["A1:A5"];
I'm trying to recolor specific cells in an excel sheet, I have found a solution by using two loops but it's simply too slow. Running through a column of 30 000 cells takes minutes.
I have never done anything like this before and I used this tutorial to get me started.
This solution uses a bool array with cells to be colored set to true.(recolored)
//using Excel = Microsoft.Office.Interop.Excel;
xlApp = new Excel.Application();
xlApp.Visible = true;
xlBook = xlApp.Workbooks.Add(Type.Missing);
xlSheet = (Excel.Worksheet)xlBook.Sheets[1];
for (int i = 1; i < columns + 1; i++)
{
for (int j = 1; j < rows + 1; j++)
{
if (recolored[j, i])
xlSheet.Cells[j+1, i+1].Interior.Color = Excel.XlRgbColor.rgbRed;
}
}
}
What I would like to do is something like this:
Excel.XlRgbColor[,] color;
//Loop to fill color with Excel.XlRgbColor.rgbRed at desired cells.
var startCell = (Excel.Range)xlSheet.Cells[1, 1];
var endCell = (Excel.Range)xlSheet.Cells[rows, columns];
var xlRange = xlSheet.Range[startCell, endCell];
xlRange.Interior.Color = color;
This one gives me an error on the final line though;
Type mismatch. (Exception from HRESULT: 0x80020005 (DISP_E_TYPEMISMATCH))
My first guess would be to make an Excel.Range object that covers the cells I want to have red and use that object in place of xlRange, something like this:
RangeObject.Interior.Color = Excel.XlRgbColor.rgbRed;
I don't know if it's possible to make an Excel.Range object with gaps like that though, I could use some help on this one.
You can select non-consecutive cells by using comma-separated list of ranges like this:
this.Application.ActiveWorkbook.ActiveSheet.Range["A2:A4,B3:B16"].Select();
You can then re-color the selection using:
Selection.Interior.Color = ColorTranslator.ToOle(Color.Yellow);
This will get rid of the coloring loop you're having trouble with.
Also, in a VSTO add-in, you should normally never need to do new Excel.Application() in your code. this.Application in the Add-in class should give you access to the active instance of Excel.
UPDATE
Here's a piece of code that should help you pin-point your problem. I added a Ribbon to my add-in and a simple button to the Ribbon. Behind the click event of this button, I have added the following code:
private void button1_Click(object sender, RibbonControlEventArgs e)
{
try
{
var App = Globals.ThisAddIn.Application;
if (App == null)
System.Windows.Forms.MessageBox.Show("App is null");
else
{
var Sheet = App.ActiveSheet;
if (Sheet == null)
System.Windows.Forms.MessageBox.Show("Sheet is null");
else
{
var Rng = Sheet.Range["A1:A5,A15:A25,A50:A65"];
if (Rng == null)
System.Windows.Forms.MessageBox.Show("Rng is null");
else
{
Rng.Select();
}
}
}
}
catch (Exception ee)
{
System.Windows.Forms.MessageBox.Show("Exception: " + ee.Message);
}
}
On my end this code runs successfully and selects the non-contiguous range of cells. Try this on your end and let me know what you see.
UPDATE 2
The same code works for me in a WinForms application with reference to Excel 14.0 (will hopefully work with Excel 12.0 too). Just a couple of minor changes are required. Here's the full code.
private void button1_Click(object sender, RibbonControlEventArgs e)
{
try
{
var App = new Microsoft.Office.Interop.Excel.Application();
if (App == null)
System.Windows.Forms.MessageBox.Show("App is null");
else
{
App.Workbooks.Add();
var Sheet = App.ActiveSheet;
if (Sheet == null)
System.Windows.Forms.MessageBox.Show("Sheet is null");
else
{
Microsoft.Office.Interop.Excel.Range Rng = Sheet.get_Range("A1");
Rng.Select();
Rng = Sheet.get_Range("A1:A5,A15:A25,A50:A65");
if (Rng == null)
System.Windows.Forms.MessageBox.Show("Rng is null");
else
{
Rng.Select();
App.Selection.Interior.Color = Microsoft.Office.Interop.Excel.XlRgbColor.rgbYellow;
App.ActiveWorkbook.SaveAs("testtest.xlsx");
App.Quit();
}
}
}
}
catch (Exception ee)
{
System.Windows.Forms.MessageBox.Show("Exception: " + ee.Message);
}
}
I had the same problem and it turned out that it was a bad list separator - in my case instead of comma there should be a semicolon.
So instead of
.Range["A1:A5,A15:A25,A50:A65"];
try:
private string listSep = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator;
.Range["A1:A5" + listSep + "A15:A25" + listSep + "A50:A65"];
[RangeObject].Interior.Color changes the cell background color. Use this
[RangeObject].Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Red);
For cell text, use
[RangeObject].Font.Color
I've struggled with this issue for a long time, too. But today I believe I finally found the solution (and the cause).
The problem is that Excel uses the current regional settings to determine the comma operator, ie. the separator between two ranges (don't ask me why - to me it's as insane as localizing the function names).
Anyway, on my computer I have the Czech locale set so the separator to use is a semicolon, not a comma! If I use it in the parameter for the Range method, it works perfectly.
From the discussion I got the impression that you are Swedish so you likely have the Swedish locale set. Its default list separator is also a semicolon, so it seems likely to me that this might solve your problem, too. You can always check the separator set in the computer regional settings by calling
System.Globalization.CultureInfo.InstalledUICulture.TextInfo.ListSeparator
Hope this helps!
The maximum length of the range address string is 255. So, you need to chunk your list so that the combined range address of each section is less than 255 long.
Does anyone know how to set the excel page break to include a certain number of columns using C# with the OpenXML SDK? What I want to do is make x columns appear on one page. I had originally thought setting the print area would do it but it doesn't. I can't find any references to do this.
This is done manually in an excel spreadsheet's "Page Break View" where you drag the vertical dotted line to include more columns.
Thanks
The OpenXML SDK distinguish between manual horizontal page breaks and manual vertical page breaks.
A manual horizontal page break allows you to specify a break above a given row Id (index).
A vertical page break allows you to specify a break to the left of the specified column Id (index).
To programmatically insert a horizontal page break use the RowBreaks and Break class.
The RowBreaks class represents a collection of all horizontal page breaks in a worksheet.
The ColumnBreaks and Break class allow you to insert a vertical page break. The
ColumnBreaks class holds all vertical page breaks for a worksheet.
The following example demonstrate the insertion of a vertical page break.
The function InsertVerticalPageBreak() takes a columnIndex (where the page break should be inserted)
and the WorksheetPart. This function first checks if the worksheet already contains a
ColumnBreaks collection. If not, one will be created. Then the function creates an instance
of the Break class and sets the Id property to the column index. I've also set the Max property
to the maximum number of rows Excel is able to handle to get a continuing vertical page break. By setting the property ManualPageBreak to true we specifing a manual page break.
I've also added a InsertHorizontalPageBreak() function to the sample to show how to
add a horizontal page break.
private void InsertPageBreaks()
{
uint columnIndex = 17U;
uint rowIndex = 51U;
using (SpreadsheetDocument sd = SpreadsheetDocument.Open("c:\\temp\\spreadsheet.xlsx", true))
{
WorkbookPart workbookPart = sd.WorkbookPart;
WorksheetPart worksheetPart = workbookPart.WorksheetParts.Last();
// Uncomment the following line to insert row page breaks.
// InsertHorizontalPageBreak(rowIndex, worksheetPart);
InsertColumnVerticalBreak(columnIndex, worksheetPart);
}
}
private void InsertHorizontalPageBreak(uint rowIndex, WorksheetPart worksheetPart)
{
Break rowBreak =
new Break() { Id = (UInt32Value)rowIndex, Max = (UInt32Value)16383U, ManualPageBreak = true };
RowBreaks rb = worksheetPart.Worksheet.GetFirstChild<RowBreaks>();
if (rb == null)
{
rb = new RowBreaks();
rb.ManualBreakCount = (UInt32Value)0;
rb.Count = (UInt32Value)0;
worksheetPart.Worksheet.Append(rb);
}
rb.Append(rowBreak);
rb.ManualBreakCount++;
rb.Count++;
}
private void InsertVerticalPageBreak(uint columnIndex, WorksheetPart worksheetPart)
{
ColumnBreaks cb = worksheetPart.Worksheet.GetFirstChild<ColumnBreaks>();
if (cb == null)
{
cb = new ColumnBreaks();
cb.ManualBreakCount = (UInt32Value)0;
cb.Count = (UInt32Value)0;
worksheetPart.Worksheet.Append(cb);
}
Break br =
new Break() { Id = (UInt32Value)columnIndex, Max = (UInt32Value)1048575U, ManualPageBreak = true };
cb.Append(br);
cb.ManualBreakCount++;
cb.Count++;
}