I'm just starting to work on a project that uses SpreadsheetGear. CopyFromDataTable is used to get all the data from a database. There are a lot of fields to work with; we're formatting them, hiding certain ranges if they're empty, etc. As an example:
SpreadsheetGear.IRange cells = worksheet.Cells;
cells["G1"].EntireColumn.NumberFormat = "m/d/yyyy";
So if the the columns are rearranged or one is removed, it appears I'd have to go back and adjust all of the hardcoded values to reflect the cell shift. Is there any way to reference the column by its first cell's name to possibly make things more modular?
I found this, but it still requires hardcoding the column.
A great alternative for the spreadsheetgear is http://officehelper.codeplex.com. Using this library you can create a template xlsx, where you do all your formatting and then you can use parts from this "template" document to create your final, to be reported xlsx files, whitout the need of programtically setting the number format, like in your example.
One possibility is to search for the heading/fieldnames
Sub test()
Dim strFieldName As String
Dim rngHeading As Range
With Sheet1
strFieldName = "Heading 3" 'find Heading Name
With .Rows("1:1") 'search row 1 headings
Set rngHeading = .Find(What:=strFieldName, LookIn:=xlValues, lookAt:=xlWhole, _
SearchOrder:=xlByColumns, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
'check if found
If Not rngHeading Is Nothing Then
MsgBox rngHeading.Address
MsgBox rngHeading.Row
MsgBox rngHeading.Column
rngHeading.EntireColumn.NumberFormat = "m/d/yyyy"
End If
End With
End With
End Sub
Related
I would like to convert a xlsx worksheet to HTML format. The worksheet has fewer Worksheet.MaxRows than the specified PageSetup.PrintArea. Therefore when converting to HTML I would like to include empty rows beyond the MaxRows in the HTML output. When changing PageSetup.PrintArea range to be lower than MaxRows (e.g. only print rows 3-15, when the final row in the excel file is 20). But when I try to increase the range (print rows 3-25, when the final row is 20), it only prints up to row 20, instead of including the extra 5 empty rows.
I am using HtmlSaveOptions.ExportPrintAreaOnly to specify the range to print.
Here's a snippet of my code:
FilePath htmlFilePath = output.GetDirPath() + output.GetFileNameWithoutExtension() + ".html";
var htmlSaveOptions = new HtmlSaveOptions
{
ExportHiddenWorksheet = false,
ExportActiveWorksheetOnly = true,
ExportPrintAreaOnly = true
};
sheet.Workbook.Save(htmlFilePath, htmlSaveOptions);
Another option I could go for is simply inserting the rows manually and removing their styling (though I seem to be having an issue applying styling but that is for another question post), but I was wondering if there is a neater, in-built way this could be done using HtmlSaveOptions.
Since you have set ExportPrintAreaOnly attribute to "true". so Aspose.Cells would print up to maximum row (data) only and do not include empty or null rows (even you have specified increased printable area). For your specific case, please either remove or comment out the following line of code, it will work ok.
ExportPrintAreaOnly = true
Moreover, if you still persist with using the above attribute for your needs, a workaround can be: you may add an empty string into your last row cell (e.g. sheet.Cells["A25"].PutValue(" ");)
PS. I am working as Support developer/ Evangelist at Aspose.
I have a very particular problem. I looked for similar problem to mine, test a lot of solution everyone proposed, but none of them is what I need.
My client need to export data sheet in excel format. Those data can be sorted, modified, rearranged, new values can be entered, some lines may disappear, some other can take their places, in short, anything can happen to those data. For example purpose, let's say that we export a list of item shown in a grocery list.
ItemID ItemName Price
Fr01 Apple 2.5
Fr02 Orange 4.0
Mt01 Beef 10.0
Mt02 Pork 8.33
Vg01 Carrot 1.25
My problem is that this data can be imported back in the software that originally created the excel to update (or add) these values in database base on the "ItemID". I already do validation if data is "correct" in value and type and interrelationality.
I tried to put a name to the range. The problem is when data is filter / sorted, the name don't follow the content, it stand still at the same position
original : (Range name is the name of the range, not an actual column)
ItemID ItemName Price || Range Name
Fr01 Apple 2.5 || data_fr01
Fr02 Orange 4.0 || data_fr02
Mt01 Beef 10.0 || data_mt01
Mt02 Pork 8.33 || data_tm02
Vg01 Carrot 1.25 || data_vg01
after sorting on ItemName:
ItemID ItemName Price || Range Name
Fr01 Apple 2.5 || data_fr01
Mt01 Beef 10.0 || data_fr02
Vg01 Carrot 1.25 || data_mt01
Mt02 Pork 8.33 || data_tm02
Fr02 Orange 4.0 || data_vg01
As you can see, all the info correctly follow, except the Range Name, so, when I try to import, I got a lot of data mismatch.
My other try was to make the NameRange an actual cell in excel. With this method, the cell follow, but can be changed, so I try to create a protected cell. Sadly, lines can't be inserted or deleted because of that. I found a workaround that consist in having names in a masked sheet, but once again, I need to synchronize sheets, which is not reliable for the same reasons mention previously.
Even worst, I must support both xls (97-2003) and xlsx.
So I'm looking for a stable workaround that will allow me to store somehow my "range name" data in the cell, making it invisible for the Excel User, but will follow the data so i can retrieve it at the right place when re-importing data.
Thanks in advance.
EDIT :
At finale, I must be able to write this property from C# application and then read back that same property with C#, and it must be compatible both excel file format, not viewable nor editable by excel user but stay with it's original value set, whatever happen to the data within the sheet except deletion (I don't mind if I just put it on the cell I wrote Apple in and not the entire range)
OK (I still think its better to add validation intelligence to the worksheet when you export but YMMV).
Try using the Range.ID string property - its not editable or visible from the Excel UI and it moves around with the cell. If the cell gets deleted it disappears. If a cell gets copied the ID property gets copied so there would be a duplicate.
It was introduced in Excel 2000 so probably won't work for Excel 97 but should be OK in all file formats for Excel 2000 to Excel 2013.
Here is some example VBA code:
Sub putids()
Dim j As Long
For j = 1 To 5
Range("a1").Offset(j - 1, 0).ID = CStr(j)
Next j
End Sub
Sub getids()
Dim j As Long
For j = 1 To 5
Debug.Print Range("a1").Offset(j - 1, 0).ID
Next j
End Sub
I think you should use some key column be it a unique name you've made up, a concatenation of the records making up your data row. Whatever. Make that as the left most column, hide it and lock it do users can't show that column or change it's contents.
Then in another worksheet, take those same values and starting in A2 paste them in.
Now in B2 enter this formula
=VLOOKUP(<this row's key value>,<Your data array in sheet1>,<column number>,FALSE)
Here is an example of how to so the fixed column/row settings
=VLOOKUP($A2,BigNamedRange,B$1,FALSE)
now Hide that sheet.
Now what you have in the first sheet is an area where your users can filter/sort/do whatever and in your second, controlled sheet, you have the data in the order you want to see it (which can be changed independently from the user's sheet).
Edit:
Click on 1: Allow Users TO Edit Ranges and set the range you want to let users edit.
Then, 2:, click Protect Sheet/Protect Workbook (which ever you need) to lock everything else.
Now your users can edit what you let them and not edit everything else
I don't see how named ranges help you.Have you thought of adding Validation code to the workbook using the before save event, so that the user cannot save data that is not valid? Or seeing how much you can do using Excel's data validation rules.Otherwise you have to read all the data and validate it later at DB update time (which is basically too late) Presumably the basic validation is that the iTemID is valid - your DB code won't care what order the data is in, and can skip empty rows etc.
Using a little of everyone suggestion and merge them.
Since any simple and normal solution isn't viable in our context and since the only possible property we can try to put something in (ID) isn't persistent and with the fact we need the client not to accidentally destroy the value considering the fact that anything may happen and will happen since there is no much restriction and the fact that we can't lock a part of the sheet without disabling line manipulation because of the side effect of the presence of a locked cell, the closest thing we were able to achieve was to insert our keys as a formatted string in column A with a weird looking formula allowing us to hide from display, then we hide the column, making it unreachable accidentally by the user.
=IF(FALSE,"our formatted string","")
Since this hidden column has data, it follows its line when sorted and trying to copy the entire line won't be possible with the fact that we select only from column B (which cause to try to insert 256 values in 255 cells) we can control a little the "false duplicate", even if not totally eliminated.
On the importer side, we just read back with a little trick comparing the formula with the value (since value is empty, only formula got our formatted data) and having a little regex to retrieve the meaning of our formatted string then doing all our validations before the actual database import.
For the rest, it will go to the training part of the user to not "delete" the data in column A, and not searching for it.
Thanks again to everyone.
I can create PowerPoint tables with many cells, but the default font (28 points) is too large. Without access to the user's PowerPoint template, how can I set the font for a table I have created? All I can do at the moment is:
Create the table.
Fill every cell with a single space because otherwise font changes are ignored
(though it works using the PowerPoint UI).
Create a range containing all cells and set font:
List<string> names = new List<string>();
foreach (PowerPoint.Column column in table.Columns)
foreach (PowerPoint.Cell cell in column.Cells) {
cell.Shape.TextFrame.TextRange.Text = "";
names.Add(cell.Shape.Name);
}
PowerPoint.ShapeRange range = ppt.ActivePresentation.Slides.Item(1).Shapes.Range(names.ToArray());
range.TextFrame.TextRange.Font.Size = 12;
I am using C# to control PowerPoint 2003..2010 via its COM API. My experiments have been with PowerPoint 2003.
In PPT 2010, this works w/o having to enter text into the cells (vba, but should be easy enough to translate). For demo purposes, I'm working with the currently selected table; substitute any other reference to the table as oTbl:
Dim oTbl As Table
Dim lCol As Long
Dim lRow As Long
Set oTbl = ActiveWindow.Selection.ShapeRange(1).Table
With oTbl
For lCol = 1 To .Columns.Count
For lRow = 1 To .Rows.Count
.Cell(lRow, lCol).Shape.TextFrame.TextRange.Font.Size = 28
Next
Next
End With
In PPT2003, as you've seen, you need to add text to the cell for the formatting to "take" (which seriously slows things down, unfortunately).
Because there are other instances in PPT automation where you may need to temporarily fill in text then delete it, you might want to write a more general routine that you can pass a shape to.
With Shape.TextFrame.TextRange
If Len(.Text) = 0 Then
.Text = "!##$%" ' or some other lunatic string not likely to be found in nature
End If
End With
"Pre-treat" the shape with this, do whatever's necessary, then "Un-treat" it with a companion routine that tests to see if the text = "!##$%" and sets it back to "" if so, leaves it alone otherwise.
I'm learning OpenXML and running into a strange issue that I can't explain, maybe someone on the forum can explain it and offer a solution.
I've been testing out some code to gather information in individual Excel cells, using some code that looks like this:
Cell theCell = wsPart.Worksheet.Descendants<Cell>().
Where(c => c.CellReference == "F4").FirstOrDefault();
if (theCell != null)
{
cellvalue = theCell.InnerText;
....
It is safe to assume I've called all my workbook parts and sheets, etc. already.
When I call this routine, and I haven't opened Excel recently, I can call Cell.InnerText and it returns the value that I expect.
But if I open the spreadsheet with Excel, make a change, then close the spreadsheet, callingCell.InnerText returns something strange, a number like "29" or "33".
Does anyone have an idea of why? Or maybe have a different alternative for getting cell values from Excel in OpenXML?
"Or maybe have a different alternative for getting cell values from Excel in OpenXML?"
Yes, it is. Cell has value tag: <v>. This tag contains integer. If you cell contains integers, this tag contains this value. But if your cell contains string, this tag contains index of string from xl\sharedStrings.xml part, wehere original cell value stores, so this value contains not the same as your cell value.
I gues when you resave your spreadsheet using Excel, it restructures your document and values could be changed as long as strings were changed indexes in xl\sharedStrings.xml part.
I guess that in the Cell you have type CellValues.SharedString (theCell.DataType) after u make a changes. That type return index of SharedStringTable. To read SharedStringTable use that Code:
Dim stringTablePart As SharedStringTablePart = WorkbookPart.GetPartsOfType(Of SharedStringTablePart).FirstOrDefault()
If stringTablePart IsNot Nothing Then
For Each item As SharedStringItem In stringTablePart.SharedStringTable.Elements(Of SharedStringItem)()
Dim text as string = item.InnerText
Next
End If
I hope this help you.
I am making an add-in and I am trying to format the output which my add-in generates,using Format as table table-styles provided by Excel.
The one which you get on the 'home tab' --> 'Format as Table' button on the ribbon.
I am using following code:
SourceRange.Worksheet.ListObjects.Add(XlListObjectSourceType.xlSrcRange,
SourceRange, System.Type.Missing, xlYesNo, System.Type.Missing).Name =
TableName;
SourceRange.Select();
SourceRange.Worksheet.ListObjects[TableName].TableStyle = TableStyleName;
TableStyleName is any style name like TableStyleMedium17, you get it if you just hover a particular style in Excel.
My problem is that, even if I keep the SourceRange as 10 columns, all the columns right till the end get selected and are considered as one table.
Because of that the table I populate right next to it is also considered as a part of the first table that was generated.Since, both the table have same column names excel automatically changes the column names in all the following tables that are generated.
Also, because I am generating the tables in a loop after 2 tables are generated I get the error :
A table cannot overlap another table.
PS: I am clearly mentioning SourceRange as:
var startCell = (Range)worksheet.Cells[startRow, startCol];
var endCell = (Range)worksheet.Cells[endRow, endCol];
var SourceRange = worksheet.get_Range(startCell, endCell);
Kindly suggest a way out.
We were able to figure out what was happening on our end for this:
on the
xlWorkbook.Worksheets.Add([before],[after], Type.Missing, Type.Missing)
call, we had to flip before and after since we wanted the sheets to move right, not left and then accessed
xlWorkbook.Worksheets[sheetCount]
by increasing sheetcount for however many sheets were being generated.
Having it the other way was creating the worksheet to access a previously assigned table formatfrom the SourceRange.Worksheet.ListObjects[TableName].TableStyle = TableStyleName call.
So, I got around this problem a week after posting this, sorry did not update in the rush of things.
This actually is an in built excel functionality.
You cant help it, the excel application will keep doing this.
So, ultimately wrote my own table styles in c# and applied it to the excel range which is mentioned as SourceRange. Its just like writing CSS.
If you are interested in knowing the details of that comment it on this question itself or you can contact me by email from my profile.