I've trawled the web and according to the documentation there doesn't appear to be a method to move a NamedRange: http://msdn.microsoft.com/en-us/library/microsoft.office.tools.excel.namedrange_methods(v=vs.80).aspx
I have the following code that copies cell data down a couple of rows:
activeSheet.Range[leftColumn + startRow, rightColumn + endRow].Copy();
//activeSheet.Range[leftColumn + startRow, rightColumn + endRow].Delete();
Range newRange = activeSheet.get_Range(leftColumn + (startRow + RowsToMoveDown.Count), rightColumn + (endRow + RowsToMoveDown.Count));
newRange.PasteSpecial(XlPasteType.xlPasteAll, XlPasteSpecialOperation.xlPasteSpecialOperationNone, Missing.Value, Missing.Value);
The NamedRange is the cells I'm copying and pasting, it does move the cell values down a couple of rows but because its a Copy it leaves the data in the above cells and the Delete method causes an Exception. However the real problem is after I move the cells the NamedRange I created:
rnArea = activeSheet.Range[leftColumn + startRow , rightColumn + (MyData.Values.Length + startRow)];
Name name = activeBook.Names.Add(uniqueName, rnArea);
Still refers to the original Cells Range (the location before I the moved the cells down).
How can I programmatically move a NamedRange in C# VSTO 4.0?
Ideally I wont have to move the cells before I put them in a Range but if this is the only solution, then I'll have to go with it.
EDIT:
After reading Doug Glancy's comment about trying the VBA like syntax in VSTO C# I came up with the following:
for (int i = 0; i < activeWorkbook.Names.Count; i++)
{
name = activeWorkbook.Names.Item(i + 1);
Debug.Write(name.Name.ToString());
System.Diagnostics.Debug.Write(name.RefersTo.ToString() + Environment.NewLine);
//prints out "Sheet1!$A$1:$A$25"
name.RefersTo = "Sheet1!$A$2:$A$26";
System.Diagnostics.Debug.Write(name.RefersTo.ToString());
//prints out "Sheet1!$A$2:$A$26"
}
But when I run this code and change the NamedRange RefersTo value, the result is that the NamedRange goes missing from the Excel NamedRange DropDownList?!?!
You can "move" a named range by adding it again with a different address. For example, in VBA:
Sub MoveNamedRange()
ActiveSheet.Names.Add Name:="test", RefersTo:="=$A$1"
Debug.Print ActiveSheet.Range("test").Address
ActiveSheet.Names.Add Name:="test", RefersTo:="=$A$2"
Debug.Print ActiveSheet.Range("test").Address
End Sub
This compiles and runs and yields the following in the immediate window:
$A$1
$A$2
EDIT - C is hard! But I managed to cobble this together in VS 2010 C#. It's from a Workbook project, but would also work in an addin I believe. I don't think I needed all the Type.Missing's in VS 2010, but I'm pretty sure I've read they are needed in earlier versions:
private void Sheet1_Startup(object sender, System.EventArgs e)
{
Globals.Sheet1.Names.Add("test", Globals.Sheet1.Range["A1"], System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing);
MessageBox.Show(Globals.Sheet1.Range["test"].Address);
Globals.Sheet1.Names.Add("test", Globals.Sheet1.Range["A2"], System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing);
MessageBox.Show(Globals.Sheet1.Range["test"].Address);
}
Related
I have my own custom file type similar to .CSV except with custom delimiters. The delimiter for the comma is (char)20 (looks like a square) and for the quotes it is (char)254 (looks like þ). I have created an Excel 2010 Add-in in Visual studio to parse through the document that replaces all custom delimiters with commas and double-quotes so that it is in .CSV format.
The program also creates a new toolbar and button that will start the process. It works fine on some documents but not on others and if you try to do it twice in one instance of Excel it comes up with the error "Cannot implicitly convert type 'System.DBNull' to 'string'". This is because the row.Text property is being read as {}.
Now my question is what is causing the row.Text property to be read as {} instead of the text that is inside the cell? Also why does this occur on some documents but not others even though they use the same encoding?
An example of what is in a cell is (NOTE-the comma symbol won't print here):
þITEM_IDþþBEGDOCþþENDDOCþþBEGATTþþENDATTþþPARENT_ATTACHMENTþþATTACHMENT_BATESþþ etc.
EDIT Here is my code:
public partial class ThisAddIn
{
Office.CommandBarButton toolbarCommand;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Office.CommandBar toolbar = Application.CommandBars.Add("My Toolbar",Office.MsoBarPosition.msoBarTop,false,true);
toolbarCommand = (Office.CommandBarButton)
toolbar.Controls.Add(
Office.MsoControlType.msoControlButton,
missing,
missing,
missing,
true);
toolbarCommand.Caption = "Toolbar Button";
toolbarCommand.FaceId = 59;
toolbarCommand.Click += new Office._CommandBarButtonEvents_ClickEventHandler(toolbarCommand_Click);
toolbar.Visible = true;
}
void toolbarCommand_Click(Office.CommandBarButton Ctrl, ref bool CancelDefault)
{
Excel.Worksheet activeWorksheet = ((Excel.Worksheet)Application.ActiveSheet);
try
{
IterateRows(activeWorksheet);
}
catch(Exception e)
{
MessageBox.Show(e.ToString());
}
Ctrl.Click+=new Office._CommandBarButtonEvents_ClickEventHandler(toolbarCommand_Click);
}
public void IterateRows(Excel.Worksheet worksheet)
{
//Get the used Range
Excel.Range usedRange = worksheet.UsedRange;
Excel.Worksheet activeWorksheet = ((Excel.Worksheet)Application.ActiveSheet);
//Iterate the rows in the used range
if (usedRange.Rows.Count > 1)
{
foreach (Excel.Range row in usedRange.Rows)
{
//MessageBox.Show(row.Text);
char quote = (char)254;
string data = row.Text;
row.Columns[1] = data.Replace(quote, '"').Replace((char)20, ',');
row.TextToColumns(Type.Missing, Excel.XlTextParsingType.xlDelimited, Excel.XlTextQualifier.xlTextQualifierDoubleQuote, Type.Missing, Type.Missing, Type.Missing, true);
}
}
}
I have a question about creating excel button and adding vba code function on it. I have created a button and module code but don't know how to make relation between them. Can anyone show me how?
my code for Button:
Excel.Shape btn = xlWorkSheet5.Shapes.AddOLEObject("Forms.CommandButton.1", Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, 300, 10, 150, 22);
Excel.OLEObject sheetBtn = (Excel.OLEObject)xlWorkSheet5.OLEObjects(btn.Name);
sheetBtn.Object.GetType().InvokeMember("Caption", System.Reflection.BindingFlags.SetProperty, null, sheetBtn.Object, new object[] { "Calculate Bus Load" });
and code for module:
String sCode = "Sub main()\r\n" +
" MsgBox \"Hello world\"\r\n" +
"end Sub";
VBA.VBComponent oModule = xlWorkBook.VBProject.VBComponents.Add(VBA.vbext_ComponentType.vbext_ct_StdModule);
oModule.Name = "Module1";
oModule.CodeModule.AddFromString(sCode);
xlWorkBook.VBProject.VBComponents.Item(1).CodeModule.AddFromString(sCode);
I have searched in Internet but didn't find anything usefull, so i cleared my mind and focused once more with c# help and I found an answer how to do it properly.
My Code:
String updateBT "...// macro code for button";
VBA.VBComponent oModule1 = xlWorkBook.VBProject.VBComponents.Add(VBA.vbext_ComponentType.vbext_ct_StdModule);
oModule1.Name = "Update";
oModule1.CodeModule.AddFromString(updateBT);
Excel.Shape btn2 = xlWorkSheet1.Shapes.AddFormControl(Excel.XlFormControl.xlButtonControl, 150, 5, 150, 22);
btn2.Name = "Update";
btn2.OnAction = "... // name of your macro code";
btn2.OLEFormat.Object.Caption = "... // Button name";
As far as I understand, you need the intermediate call to code/macro module from the button when you click on the button. So the code gets triggered and does what you want it to do.
In usual manner, for e.g.
we add a button in Excel Sheet
choose on_click event
add a code like call mySub
You need to do that within C#.
Please adjust for your module and control names. Here is a sample.
//Within your above code add,
sheetBtn.Click += new MSForms.CommandButtonEvents_ClickEventHandler(sheetBtn_Click);
}
//button click event triggers
void sheetBtn_Click()
{
call subMain
// try a test using : MessageBox.Show("button test!");
}
** PLEASE TRY THIS TUTORIAL OUT It has pretty much what you need.
As per the subject on just invoking a sheet sub or module sub written in Excel from C#, you may use run macro method.
//instead of this.application, you call refer to the Excel app object
this.Application.Run("yourMacroName",missing,missing........)
Reference:
I am working on a project that writes data to an Excel file.
Everything is finished now, however I need a few cells with a bigger size than the rest (title, etc).
I have read about this about the internet, but I keep having the same problem: when I execute my code (see below for what I have tried), everything in the worksheet becomes larger.
What I already have tried:
worksheet.Rows[1].Cells[7].Style.Font.Size = 20;
worksheet.get_Range("A7", "A7").Style.Font.Size = 20;
None of this seems to work; what is the correct way to increase a cell's font size?
I had to use:
worksheet.get_Range("A7", "A7").Cells.Font.Size = 20;
If the data is consistent and will always be written to the same cells then this is the simplest solution - works well for product details / contact info type exporting
// set cell A7
worksheet.get_Range("A7", "A7").Font.Size = 20;
// set cells A7, A8
worksheet.get_Range("A7", "A8").Font.Size = 20;
// set cells A7, B7
worksheet.get_Range("A7", "B7").Font.Size = 20;
// set cells A7, A8, B7, B8
worksheet.get_Range("A7", "B8").Font.Size = 20;
If the data varies and will sometimes be written to multiple rows/columns then something like this is more simple - works well for dataset / shopping list type exporting
int RowNum;
int ColNum;
// some code to set variables
worksheet.Cells[RowNum, ColNum].Font.Size = 20;
I would just use:
worksheet.Range["A7"].Style.Font.Size = 20;
edit: sorry, wrong brackets
When working with interop excel, try not to write your code with "two dots" in order to clean interop excel objects.
This also helps having your code more readable.
Anyway, to answer your question, and using what I have pointed out... all you have to do is:
//Declare your variables
Application excel = null;
Workbook excelworkBook = null;
Range excelCellrange = null;
Worksheet worksheet = null;
Font excelFont =null;
//start your application
excel = new Application();
try
{
...
//your code goes here...
excelCellrange = worksheet.Range[worksheet.Cells[1,7],worksheet.Cells[1,7]];
excelFont = excelCellrange.Font;
excelfont.Size = 20;
...
...
}
catch(Exception ex){
}
finally{
//here put something to clean the interop objects as the link above.
...
Marshal.ReleaseComObject(excelfont);
...
}
I wanted to add formulas to an Excel workSheet.
I managed to do so with the Formula property.
The problem is that when I open the worksheet in Excel, I can see that the formula works - but I can only see the result in the cell. I can't see the formula that was calculated in the Formula Bar at the top of Excel.
Obviously if I enter a formula in Excel itself I can see the result in the cell and the formula in the Formula Bar.
Some of my code:
for (int i = 0; i < nOfColumns / 3; i++)
{
Range cells = workSheet.Range[workSheet.Cells[2, i * 3 + 3], workSheet.Cells[lastRowNumber, i * 3 + 3]];
cells.FormulaR1C1 = "=IF(EXACT(RC[-2],RC[-1]),TRUE,ABS(RC[-2]/RC[-1]-1))";
}
below is a test code. even after I save the workbook - the FormulaHidden is false and I can successfully retrieve the formula insterted. really frustrated
Microsoft.Office.Interop.Excel.Application excelApp = null;
Workbooks workBooks = null;
Workbook workBook = null;
Worksheet workSheet;
try
{
excelApp = new Microsoft.Office.Interop.Excel.Application();
excelApp.DisplayAlerts = false;
workBooks = excelApp.Workbooks;
workBook = workBooks.Open(filePath, AddToMru: false);
workSheet = workBook.Worksheets.get_Item(1);
int nOfColumns = workSheet.UsedRange.Columns.Count;
int lastRowNumber = workSheet.UsedRange.Rows.Count;
Range rng = workSheet.Range["C1"];
rng.Formula = "=SUM(B2:B4)";
String formula = rng.Formula; //retrieve the formula successfully
rng.FormulaHidden = false;
workSheet.Unprotect();
workBook.SaveAs(filePath, AccessMode: XlSaveAsAccessMode.xlExclusive);
formula = rng.Formula; //retrieve the formula successfully
bool hidden = rng.FormulaHidden;
}
catch (Exception e)
{
throw;
}
finally
{
if (workBook != null)
{
workBook.Close();
workBook = null;
}
if (workBooks != null)
{
workBooks.Close();
workBooks = null;
}
if (excelApp != null)
{
excelApp.Quit();
excelApp = null;
}
}
}
Anyone know how to make the formula shown, when adding the formulas programatically ?
finally !!! figured it out. this behavior is caused by the SaveAs flags.
changed
workBook.SaveAs(filePath, AccessMode: XlSaveAsAccessMode.xlExclusive);
to
workBook.SaveAs(filePath, AccessMode: XlSaveAsAccessMode.xlShared);
now the only thing left is to understand what exactly is the different between the two flags. :)
Hiding the formula (by checking Hidden checkbox on Format Cells dialog) & protecting the worksheet (thereafter) will cause the formula to not show in the formula bar.
Example VBA code
Range("C1").FormulaHidden = True 'set this property to false to make formula visible.
Sheet1.Protect
EDIT: In order to see the formula in the formula bar
Range("C1").FormulaHidden = False
Sheet1.Unprotect
Go to the Formula tab on the tool bar, and click "Show Formulas".
I think localization could be involved in this weird behaviour.
Some time ago, working in Excel, I had the impression that formulas got stored in localized language (I was using italian), then undergo a conversion when compiled. This could make sense, because localized constants are an essential part of the spreadsheet data.
I'm sorry I haven't now Excel available, so I can't be more precise, but I think you could try to localize to english your spreadsheet, or set the formula text in your local language.
So I have a Microsoft.Office.Interop.Excel.Workbook object. It basically uses a template Excel file to construct itself. The Excel file contains a template column color for the results section, etc. and then the code basically just prints over those template columns, it doesn't actually customize the look of the file itself, only puts the data into it.
However, this is an issue because after it's done, our template accounts for the most POSSIBLE rows it can, but a lot of the times (most of the time), we use not even half of them.
What's the easiest way to remove all rows that DO NOT have cell data in them after the file has been created, working directly with the Microsoft.Office.Interop.Excel.Workbook object. We already have a "cleanup" method that runs after creation, but I want to add that logic to it. Here's our current cleanup:
private void CleanupExcel()
{
if (!_visible && _workbook != null)
{
_workbook.Close(false, Missing.Value, Missing.Value);
}
_workbook = null;
_sheet = null;
if (_excel != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(_excel);
// WW, 5/26/09: not sure if a problem here, but it probably is since the code was taken from here
// but in the indicator, Excel exists in the process even after the app is closed. The code here seems to fix it.
GC.Collect();
GC.WaitForPendingFinalizers();
}
_excel = null;
}
P.S. It's the first of two sheets in the document by the way. I also have access to the Microsoft.Office.Interop.Excel.Worksheet object if it's easier to do that way.
Assuming that all the empty rows are at the bottom of the sheets, you should be able to select them as a range and then delete them all, something like this I think:
Excel.Range range = _sheet.get_Range("A501", "A60000");
Excel.Range row = range.EntireRow;
rowDelete(Type.Missing);
If they're not at the bottom, maybe you could do a sort so that they all end up at the bottom and then use something similar to my code.
Try the following. It basically goes through a range (which I've hard-coded to be A1:A10), checks which rows are empty, marks them for deletion, then sweeps though and deletes them.
public void RemoveRows()
{
Excel.Range rng = Application.get_Range("A1", "A10");
List<int> rowsMarkedForDeletion = new List<int>();
for(int i = 0; i < rng.Rows.Count; i++)
{
if(Application.WorksheetFunction.CountA(rng[i + 1].EntireRow) == 0)
{
rowsMarkedForDeletion.Add(i + 1);
}
}
for(int i = rowsMarkedForDeletion.Count - 1; i >= 0; i--)
{
rng[rowsMarkedForDeletion[i]].EntireRow.Delete();
}
}
To give full credit, using COUNTA is a technique I learned from OzGrid.