I'm trying to use Interop.MSProject with C# to do something that conceptually, should be the simiplest thing in the world to do. However, I am having some trouble with it's cryptic api which has minimal documentation. All I wish to do is to find a row that contains a specific string in one of it's columns (cell) and remove that row. After I've done that, I just want to display the modified project file so that the user has the option to save it. Here is what I've tried:
MSProject.Application app = new MSProject.Application();
app.FileOpenEx(
filePath,
false,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
MSProject.PjPoolOpen.pjPoolReadWrite,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing);
foreach(MSProject.Task task in proj.Tasks)
{
if (task == null) continue;
string cellValue = task.OutlineCode3;
if (cellValue == searchString)
task.Delete();
}
app.Visible = true;
It seems as though task.Delete is not working. I've even tried to generalize this code to the following:
foreach (MSProject.Task task in proj.Tasks)
task.Delete()
and this did not work either. Does anyone know a way that I can remove a task or a row base on a value in one of the rows cells?
I faced the same conundrum yesterday.
Your approach is correct but it doesn't work because the tasks are Index-based. This is more obvious in a straight for loop.
If you have 10 tasks and delete one, let's say number 5, now the next task (6) is now 5 but your loop will try to delete 6 which is now 7. This will mess everything up.
If you want to delete all tasks you should do something like:
// Task Indexes are 1-based not 0-based
for(int i = 1; i <= proj.Tasks.Count; i++) {
proj.Tasks[i].Delete();
i--;
}
Following this train of thought let's apply this to a foreach loop
foreach(Task t in tasksToDelete) {
proj.Tasks[proj.GetTaskIndexByGuid(t.Guid)].Delete();
}
Why am I using GetTaskIndexByGuid? It seems that the Index property of each task if not properly (or immeadiately) updated when you delete a task but I found that using GetTaskIndexByGuid always returns the correct index.
Hope it helps
Related
After use MATCH formula to validationStr, my class went wrong.
Can anyone tell me why error happened?
ERROR:
System.Runtime.InteropServices.COMException: 'Exception from HRESULT: 0x800A03EC'
Thank you very much!
using Microsoft.Office.Interop.Excel;
string validationListStr = "=OFFSET($AB$12,1,MATCH($T$12,$AB$12:$AD$12,0)-1,COUNTA(OFFSET($AB$12,1,MATCH($T$12,$AB$12:$AD$12,0)-1,100,1)),1)";
workSheet.Cells[1, 1].Validation.Delete();
workSheet.Cells[1, 1].Validation.Add(XlDVType.xlValidateList, XlDVAlertStyle.xlValidAlertInformation, Type.Missing, validationListStr, Type.Missing);
workSheet.Cells[1, 1].Validation.IgnoreBlank = true;
workSheet.Cells[1, 1].Validation.InCellDropdown = true;
I set the formula to validation by hand in Excel. There was no problem.
However, I cannot set the formula by C#.
The wrong 'Missing' object is being passed to the Add function.
System.Type.Missing needs to be changed to System.Reflection.Missing.Value
workSheet.Cells[1, 1].Validation.Add(
XlDVType.xlValidateList,
XlDVAlertStyle.xlValidAlertInformation,
Missing.Value, // <-
validationListStr,
Missing.Value // <-
);
I'm trying to move column B in front of column Q in an excel sheet as part of a report I'm working on. I have experience in VBA but relatively little in c# so I've spent the last hour on Google and can't find a solution, I feel like this should be simple but I can't quite get it.
Method one, which results in a “Insert method of Range class failed” msg.
Excel.Range rngCut1 = JobLabourSheet.get_Range("B:B", Type.Missing);
Excel.Range rngPaste1 = JobLabourSheet.get_Range("Q:Q", Type.Missing);
rngCut1.Columns.Cut(rngPaste1.EntireColumn.Insert(Excel.XlInsertShiftDirection.xlShiftToRight, rngCut1));
Method two results in a “Unable to get a Cut property of the Range class” msg.
Excel.Range rngCut1 = JobLabourSheet.get_Range("B:B", Type.Missing);
Excel.Range rngPaste1 = JobLabourSheet.get_Range("Q:Q", Type.Missing);
rngCut1.Columns.Cut(rngPaste1.EntireColumn.Insert(Excel.XlInsertShiftDirection.xlShiftToRight, Missing.Value));
In the second method when I omit the CopyOrigin I get the msg but it does insert a blank column in front of column Q.
In VBA I would use the following:
Columns("B:B").Cut
Columns("Q:Q").Insert Shift:=xlToRight
But like I said, my c# experience is limited at the moment so I have no idea how to go about translating it to c#
This isn't very intuitive, but this is how I got it to work. I took an "insert" range and used the Insert() method and passed a "range.Cut()" method as the "Copy Origin" parameter.
Reference docs:
http://msdn.microsoft.com/en-us/library/microsoft.office.interop.excel.range.insert.aspx
http://msdn.microsoft.com/en-us/library/microsoft.office.interop.excel.xlinsertshiftdirection.aspx
http://msdn.microsoft.com/en-us/library/microsoft.office.interop.excel.range.cut.aspx
Here is a sample app (be sure to add a reference to Microsoft.Office.Interop.Excel):
using System;
using System.Collections.Generic;
using System.Text;
using Excel = Microsoft.Office.Interop.Excel;
namespace ExcelCutAndInsertColumn
{
class Program
{
static void Main(string[] args)
{
Excel.Application xlApp = new Excel.Application();
Excel.Workbook xlWb = xlApp.Workbooks.Open(#"C:\stackoverflow.xlsx");
Excel.Worksheet xlWs = (Excel.Worksheet)xlWb.Sheets[1]; // Sheet1
xlApp.Visible = true;
// cut column B and insert into A, shifting columns right
Excel.Range copyRange = xlWs.Range["B:B"];
Excel.Range insertRange = xlWs.Range["A:A"];
insertRange.Insert(Excel.XlInsertShiftDirection.xlShiftToRight, copyRange.Cut());
}
}
}
I have the following function =AND(EXACT(B3;F3);EXACT(F3;J3)) which returns TRUE or FALSE.
I'd like to create a cell rule for coloring red the false and green the true values.
Trying to use the following code, but not working, what am i doing wrong?
Excel.FormatConditions fcs = xlWorkSheet.Cells[i,"M"].FormatConditions;
Excel.FormatCondition fc = (Excel.FormatCondition)fcs.Add(Excel.XlFormatConditionType.xlTextString, Excel.XlFormatConditionOperator.xlEqual, "TRUE");
Excel.Interior interior = fc.Interior;
interior.Color = ColorTranslator.ToOle(Color.LightGreen);
Excel.Font font = fc.Font;
font.Color = ColorTranslator.ToOle(Color.ForestGreen);
fc = (Excel.FormatCondition)fcs.Add(Excel.XlFormatConditionType.xlTextString, Excel.XlFormatConditionOperator.xlEqual, "FALSE");
interior.Color = ColorTranslator.ToOle(Color.LightSalmon);
font.Color = ColorTranslator.ToOle(Color.Red);
You are not associating the colors with the given rules (but with variables not related to the Conditional Formatting). Also you should better rely on xlCellValue. This code delivers what you are after:
Excel.FormatCondition fc = (Excel.FormatCondition)fcs.Add(Excel.XlFormatConditionType.xlCellValue, Excel.XlFormatConditionOperator.xlEqual, "TRUE", Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
fc.Interior.Color = ColorTranslator.ToOle(Color.LightGreen);
fc.Font.Color = ColorTranslator.ToOle(Color.ForestGreen);
fc = (Excel.FormatCondition)fcs.Add(Excel.XlFormatConditionType.xlCellValue, Excel.XlFormatConditionOperator.xlEqual, "FALSE", Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
fc.Interior.Color = ColorTranslator.ToOle(Color.LightSalmon);
fc.Font.Color = ColorTranslator.ToOle(Color.Red);
(Apologize for posting this as an answer, but I don't have enough rep to add a comment yet)
You have line:
Excel.FormatCondition fc = (Excel.FormatCondition)fcs.Add(Excel.XlFormatConditionType.xlTextString, Excel.XlFormatConditionOperator.xlEqual, "TRUE");
And your third parameter "TRUE" would normally represent the formula. If you want this to work, you'll need to change it to "=TRUE". Similarly, you have "FALSE" which should be updated to "=FALSE".
You'll also want to include #varocarbas suggestions above.
I`m trying to get copy of one worksheet to another worhsheet,below is my code..I tried to release instances created of excel but still I see one instace of it in TaskManager.
C# Code:
try
{
wBook = xCel.Workbooks.Open(filePath);
xCel.Visible = false;
this.xCel.DisplayAlerts = false;
wBook = (Excel.Worksheet)wBook.Worksheets.get_Item(1);
wBook.Copy(Type.Missing, Type.Missing);
wBook = (Excel.Worksheet)wBook.Sheets[1];
wBook.SaveAs(strFileCopyPath);
}
finally
{
if (wBook != null)
{ wBook.Close();
Thread.Sleep(500);
}
Marshal.ReleaseComObject(wBook);
Marshal.ReleaseComObject(wBook);
}
Please some one tell what wrong i`m doing here?
thanks
You are doing nothing SANE wrong - but the concept of closing exel is very harebrained.
Thats because:
1: Excel creates sometimes intermediate objects, which are NOT visible for you, especially if you do something like this: DIm bla = excel.worksheet.range("A1").value. Then there are intermediate objects for excel.
2: The excel objects have to be closed in a very specific order.
This code SHOULD help:
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
this has to be double due to the fact that pass 1 only marks intermediate objects, but does not destroy them. It is garbage collection.
Also destroy your objects in this order:
Ranges etc.
Worksheets
Workbook
APP
like this:
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlSheet)
xlsheet = nothing
xlBook .Close()
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlBook )
xlbook = nothing
xlapp.quit
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlapp)
xlapp = nothing
use try catch to catch errors in this section, if an object is nothing..
You forgot to call Quit() method of Excel.Application object. Ususally I use the following code to close Excel (VB.Net code snippet):
xlApp.Quit()
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp)
xlApp = Nothing
xlBook = Nothing
xlSheet = Nothing
There's something weird about get_Caller(Type.Missing) method. It returns a negative integer, -2146826265, instead of the Range object as it should.
Has anyone come across this issue before? Why is that and how should I solve it?
Thanks.
Excel.Range range = (Excel.Range) application.get_Caller(System.Type.Missing);
The above code would fail if I try to explicitly user type Excel.Range. The error message says, 'Cannot convert type 'int' to 'Microsoft.Office.Interop.Excel.Range'.
EDIT:
The intention of getting caller of the cell is to pass it to my following function:
private string getResultFromResultSheet(Excel.Range originalSheetRange, Excel.Worksheet resultSheet)
{
string DataResult = "";
try
{
string os_currentAddress = originalSheetRange.get_Address(Type.Missing, Type.Missing, Excel.XlReferenceStyle.xlA1, Type.Missing, Type.Missing);
Excel.Range currentRRange = null;
currentRRange = resultSheet.get_Range(os_currentAddress, Type.Missing);
if (currentRRange != null)
{
if (string.IsNullOrEmpty(Convert.ToString(currentRRange.Value)))
DataResult = "";
else
DataResult = Convert.ToString(currentRRange.Value);
}
}
catch (Exception ex)
{
}
return DataResult;
}
With the return value from that function, I can pass it back to UDF and display it in the original cell. Is there any better way to implement the function?
Review the table at the end of this MSDN Library article about the Application.Caller property. You've discovered the value of the #REF! error. Google 'excel error 2023' for additional info. I kinda doubt you can use this property, given that the caller is your C# program.