Outlook Calendar Cleaner

I encountered an odd problem while working with a customer that was porting from Lotus Notes to Office 365 (Exchange) and ended up creating a new tool called the Outlook Calendar Cleaner.

The customer had a mixed environment of Mac and Windows and in certain conditions appointment items were disappearing on the Mac Office Outlook calendar, but still appearing in Outlook Web Access (OWA) and if the delegate was in Windows, they would still see the appointment on their view of the owners calendar.

The issue turns out to be that in the conversion a multi-line subject in an appointment item (supported in Lotus, but not in Exchange), has the new line character converted to a start of text (SOT) character or ASCII code 0x02. The problem is that this is an invalid character in XML and the Exchange Web Services implementation on Mac O/S X does not properly parse this character. This causes the process to hang and all appointments which were being imported on that thread are not copied over. Net effect – they appear to be missing in Mac Outlook.

So, I created a tool to correct the problem. The Outlook Calendar Cleaner is a very specific tool targeting this problem. You can to open the users Exchange Account (Office 365 account) in Outlook 2010 for Windows and run the tool in Windows and it will clean any appointments found to have this special character.

I have posted the Outlook Calendar Cleaner tool on Codeplex (along with the source code). Here:

https://outlookcalendarclean.codeplex.com/

Excel File Cleaner

Last year I worked with a major fortune 500 company on an issue they were having with their Excel files increasing in size by major proportions, slow load times, and cell formatting errors. Each of the problems are documented by well known issues in the Microsoft Knowledge Base:

The company had problems with the workarounds listed in the articles. Those tools/options did not work well because they targeted files created in Excel 2003 and did not take into account the extended cell range capabilities in Excel 2007/2010. As such I created a new tool to help combat this issue. They have since deployed this tool throughout their organization to great success.

Just recently, in an effort to increase the adoption of this tool, I placed it on CodePlex (source code and all). The tool uses the OpenXml toolkit to correct files that exhibit issues (file size, slowness and cell format errors). You can access it here:

Excel File Cleaner

Detect When an Excel Chart is Deleted

A customer of mine (thanks Simon! Hot smile) contacted me with a solution he discovered while trying to determine if a user deleted a chart from a workbook. The following code belongs in a Ribbon.cs with a button to insert the chart.

When the user clicks the button to add the chart, the deactivate event is then attached to the chart. The trick is to throw an exception to detect the deletion. When the chart is deleted, the deactivate event will fire, but any attempt to reference any property of the chart will fail with an exception.In this case an attempt is made to access the “.Name” property of the chart. If it is deleted, it will throw an exception and tell you the chart was deleted. Here is the code:

Excel.Chart chart;
private void button1_Click(object sender,
                           RibbonControlEventArgs e)
{
    try
    {
        // hook the deactivate event
        chart = Globals.ThisAddIn.Application.ActiveChart;
        if (chart != null)
        {
            chart.Deactivate += new
                Excel.ChartEvents_DeactivateEventHandler(
                        chart_Deactivate);
        }
    }
    catch (Exception ex)
    { }
}

/// <summary>
/// Caused when a selected chart is deactivated.
/// </summary>
void chart_Deactivate()
{
    try
    {
        string a = chart.Name;
        MessageBox.Show("Chart has been deselected " +
                        " but it is still around");

    }
    catch
    {
        MessageBox.Show("Chart has been deleted!");
        return;
    }
}

Moving Entire Rows in Excel

I had an interesting request today that I think is worth sharing. Hot smile I was essentially asked, what the most efficient way of moving a row from one point in a worksheet to a lower point in the sheet. Such as moving Row 6 to Row 42. I developed the following method to do this:

 

private bool MoveRow(Excel.Range rngRowToMove, int moveRowNum)
{
    // get reference to the sheet
    Excel.Application xlApp = rngRowToMove.Application;
    Excel.Worksheet ws = (Excel.Worksheet)rngRowToMove.Application.ActiveSheet;
    int rowToDelete = rngRowToMove.Row; // and the original row number

    // verify that the moveRowNum is further down (below/higher number) than
    // the rngRowToMove location. If it is not then we fail…
    if (moveRowNum <= rowToDelete)
        return false;

    try
    {
        // now grab the row where we want to move to
        Excel.Range rngMoveTo = ((Excel.Range)rngRowToMove.Application.Cells[moveRowNum, 1]).EntireRow;
        // insert a row so as to not delete the data already there
        // but only do it if it is not the last row in the sheet
        if(moveRowNum < ws.UsedRange.Rows.Count)
            rngMoveTo.Insert(Excel.XlInsertShiftDirection.xlShiftDown, Excel.XlInsertFormatOrigin.xlFormatFromLeftOrAbove);
        // and move…
        rngRowToMove.EntireRow.Cut(rngMoveTo);
        // now go to delete the empty row
        ((Excel.Range)xlApp.Cells[rowToDelete, 1]).EntireRow.Delete(Excel.XlDeleteShiftDirection.xlShiftUp);
        return true;
    }
    catch
    {
        return false;
    }
}

 

To call this you can use a line like this:

MoveRow(xlApp.ActiveCell, ((Excel.Worksheet)xlApp.ActiveSheet).UsedRange.Rows.Count + 1)

 

What this does is moves the currently selected row (if the selection is anywhere in a given row), to the very last row of the sheet.

Auto Recovered Document Doesn’t Fire Events

I ran into an interesting scenario recently in which an Auto Recovered Document – opened by the user from the Document Recovery pane does not fire the DocumentOpen, DocumentChange or WindowActivate events. This is because when you open a document from the pane, the version of the file open from the file system is closed and the version from the document recovery replaces it. This replacement happens without firing any events.

image

This can cause problems where, for example, you need to replace VSTO controls on the document face upon open. What you will end up with is a document that is open, but may not be properly connected to your add-in because no events fired.

In a document management system, Auto Recovery may cause more problems than it is worth. So my suggestion is – for scenarios like this – it is best to disable it for just those documents that are part of your Document Management System. Fortunately, there is a feature in Microsoft Word to disable Auto Recovery per document, see below:

clip_image002

And this is available from the Object Model as well:

void Application_WorkbookOpen(Microsoft.Office.Interop.Excel.Workbook Wb)
{
    // check to see if the document is part of documet management system
    if (isManagedDocument())
        Wb.EnableAutoRecover = false; // disable AutoRecovery just for this wb
}

 

This option when set on your managed documents will prevent the Auto Recovery situation described above, but it will also prevent the “safety net” that Auto Recovery will provide your users should something like the power go out. If Auto Recovery is needed one solution may be to create a similar feature into your add-in.

How to Determine if a Workbook is Embedded

Recently I had a very interesting request from a customer. How do you determine if a workbook is embedded inside another document and opened in Excel for editing? Excel has a property that used to help in this regard:

if (Wb.IsInplace)
{
    // do something here
}

 

MSDN states that this is to help you determine if the document is being edited in-place. In-place being the operative term for embedded. However, this now seems to always return FALSE since Office applications no longer really support OLE in-place editing in the traditional sense. So the predicament. How can you determine this state now… Confused smile

Well, I found a way, across two events and a few properties and looking at a few other properties and making some educated decisions based on those properties values. Hot smile

The way this works is to determine two different conditions:

  1. If the Caption changed from the Open Event to the Activate event and there is no Path to the file, then we know it is embedded as that is the only state in which this occurs.
  2. In condition where we are New and the Open event does not fire, the caption name in the Activate event is “object” so we look for that condition if condition #1 is not true.

Here is the code:

private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
    // hook to events
    Application.WorkbookOpen += new Microsoft.Office.Interop.Excel.AppEvents_WorkbookOpenEventHandler(Application_WorkbookOpen);
    Application.WorkbookActivate += new Microsoft.Office.Interop.Excel.AppEvents_WorkbookActivateEventHandler(Application_WorkbookActivate);
}

string wbOpenCaption = "";
void Application_WorkbookOpen(Microsoft.Office.Interop.Excel.Workbook Wb)
{
    // get the captions name because iof this is an embedded workbook
    // the name will have changed by the time we get to the Activate
    // event and that is a sure sign of an embdedded state
    wbOpenCaption = Wb.Windows[1].Caption.ToString();
}

void Application_WorkbookActivate(Microsoft.Office.Interop.Excel.Workbook Wb)
{
    // on activate we see if the OPEN document caption has changed.
    // When the user has opened a workbook enbedded anywhere, the intial
    // caption for the window will be something like "Book2" in the
    // Workbook_Open event. However, by the time we get the Activate
    // event the caption will be updated to "Workbook in Document.docx"
    // So here we are looking for that condition along with the
    // condition where the workbook has no path. This assures us we
    // have an embedded document.
    //
    // In scenarios where a new workbook is just now embeeded into a
    // document the Caption for the window will be "Object" also in
    // those scenarios the Open event is not called.
    string wbActivateCaption = Wb.Windows[1].Caption.ToString();
    if (wbOpenCaption.Length > 0 &&
        wbOpenCaption != wbActivateCaption &&
       Wb.Path.Length == 0)
    {
        MessageBox.Show("This is an embedded workbook.");
    }
    else if (wbOpenCaption.Length == 0 &&
            wbActivateCaption.ToLower() == "object")
    {
        MessageBox.Show("New embedded workbook.");
    }
    wbOpenCaption = ""; // reset
}

Creating a User Defined Function in C#

UPDATE: See latest post: http://davecra.com/2013/06/08/update-creating-excel-udfs-in-c/

UPDATE: See POC example project: http://davecra.com/2014/02/25/codeplex-loading-an-excel-udf-from-vsto/

…and replacing a formula at runtime. Open-mouthed smile

In this posting, I am going to tackle two things at once:

  • The first is writing an Excel User Defined Function (UDF) using an Add-in via C#.
  • The second is a far more difficult proposition of having the function replace the formula in the evaluated cell.

I was working at a customer who has some legacy UDF’s still in VBA Add-ins (XLA or XLAM files). I strongly advise clients against mixing and matching VSTO and VBA. So, I showed them how to move their UDF’s completely to a C# add-in.

Additionally, in evaluating their issue, they want to replace their legacy UDF with a simple placeholder function that will repeat the value given. So, it boils down to taking the existing formula while it is being evaluated and replacing it. Excel will not let you do this… easily. Hot smile

The following code demonstrated how to define a UDF in C# and then to replace a formula while it is being evaluated:

[GuidAttribute(“D94AF1AD-7E2A-4611-AA6F-47351FF46ACD”)]
public interface IFunctions
{
string UDF_Replace(object a);   // new interface
string UDF(object a, object b); // legacy interface
}///<summary>
/// This is how COM will see the class for the functions
/// using the interface defined above. This has to use
/// the IDTExtensibility interface as well. You cannot
/// use VSTO as it short-circuts the extensibility for
/// you automatically and there is no way to register
/// it to be seen in this way…
///</summary>
[GuidAttribute(“1A43DEAA-EE1D-4e0d-8CC1-79B3998B7CEB”),
ProgId(“ExcelFunctionsExample.Connect”),
ClassInterface(ClassInterfaceType.AutoDual)]
[ComDefaultInterface(typeof(IFunctions))]
public class Connect : Object, Extensibility.IDTExtensibility2, IFunctions
{
public Connect() { } // constructor
// These 6 methods are required for the IDTExtensibility2 interface
public void OnBeginShutdown(ref System.Array a) { }
public void OnStartupComplete(ref System.Array a) { }
public void OnAddInsUpdate(ref System.Array a) { }
public void OnDisconnection(Extensibility.ext_DisconnectMode e,
ref System.Array a) { }
public void OnConnection(object application,
Extensibility.ext_ConnectMode e,
object oo, ref System.Array a) { }
// These next three methods are registering the DLL with COM
[ComRegisterFunctionAttribute]
public static void RegisterFunction(Type type)
{
Registry.ClassesRoot.CreateSubKey(GetSubKeyName(type));
}
[ComUnregisterFunctionAttribute]
public static void UnregisterFunction(Type type)
{
Registry.ClassesRoot.DeleteSubKey(GetSubKeyName(type), false);
}
private static string GetSubKeyName(Type type)
{
string s = @”CLSID\{“ + type.GUID.ToString().ToUpper() +
@”}\Programmable”;
return s;
}

///<summary>
/// This function is the new user defined function
///</summary>
///<param name=”a”></param>
///<returns></returns>
public string UDF_Replace(object a)
{
// >> Do your work here <<
return a.ToString();
}

///<summary>
/// This function is the legacy user defined function
///</summary>
///<param name=”a”></param>
///<param name=”b”></param>
///<returns></returns>
public string UDF(object a, object b)
{
// get reference to the cell where the function
// is being evaluated
Excel.Range thisRange = (Excel.Range)
((Excel.Range)a).Application.get_Caller(1);
// get the value in the cell…
string ret = thisRange.Value2.ToString();
// You cannot change a formula while you are inside the
// evaluation of that formula. But you can spawn off a
// thread so that this will get processed after this
// function exits…
new Thread(new ThreadStart(delegate
{
// replace the formula
thisRange.FormulaR1C1Local = “=UDF_Replace(“ + ret + “)”;
})).Start();
return ret; // return
}
}

 

To build this, you simply need to create a Shared Add-in Project in Visual Studio, like this:

image

Once you place the code above in place, you might want to get your own GUID. Click Tools > Create GUID:

image

Adding Controls to a Worksheet at Runtime

I was working on an issue with one customer and in order to test a hypothesis and see if we could reproduce a problem quickly, I needed to be able to place a lot of VSTO controls on an Excel Spreadsheet in a rapid manner. So I created a Ribbon, put a button on the Ribbon and then added the following code to the button:

// get reference to the Excel application from the ThisAddIn
Excel.Application xlApp = Globals.ThisAddIn.Application;
Tools.Workbook wb = xlApp.ActiveWorkbook.GetVstoObject();
// get the VSTO tools worksheet object
Tools.Worksheet ws = ((Excel.Worksheet)wb.ActiveSheet).GetVstoObject();

// Create a button
System.Windows.Forms.Button btn = new System.Windows.Forms.Button();
btn.Text = "X";
// create an event handler for click
// NOTE: This is an inline delegate
btn.Click += ((pSender, pE) => MessageBox.Show("Hello World"));
// create an event handler for the mouse enter event that
// will pop up a ToolTip.
btn.MouseEnter += ((pSender, pE) =>
{
    // create a tooltip on the fly and attach it to the
    // button so that when the user hovers over it, it shows
    System.Windows.Forms.ToolTip tt = new ToolTip();
    tt.ToolTipIcon = ToolTipIcon.Info;
    tt.SetToolTip(btn, "Hello World!");
});
// Now build a user control, add the button to it and then
// add the control to the form
UserControl uc = new UserControl();
btn.Dock = DockStyle.Fill;
uc.Controls.Add(btn);
// NOTE: When we add the control to the form we need to specify
// the name of the control and to prevent a conflict we name
// the control as a GUID
ws.Controls.AddControl(uc, (Excel.Range)xlApp.Selection,
    Guid.NewGuid().ToString());

 

You may see a new construct for an inline delegate (introduced in C# 3.0). The customer I was working with showed this new method to me and I was amazed at the simplicity: Hot smile

btn.Click += ((pSender, pE) => MessageBox.Show("Hello World"));

 

Well, now with this additional form for a delegate, there are three ways that you could technically write this code:

btn.Click += new EventHandler(btn_Click);
btn.Click += delegate(object pSender, EventArgs pE) { MessageBox.Show("Hellow World!"); };
btn.Click += ((pSender, pE) => MessageBox.Show("Hello World"));

 

The first is the more traditional Event argument using the EventHandler() pre-defined delegate. The second is another form of inline delegate and one I was more familiar with until I was introduced to the new inline delegate form on the 3rd line. This is officially called lambda expressions. You gotta love C#. Open-mouthed smile

AfterClose event

A common theme from customers I work with who are developing some type of document management system is that they need to know when a file is closed. Completely closed.

The BeforeSave events let you know just before the file closed and allows you the change to cancel the event, but there is no AfterClose event.Common scenarios are they need to be able to grab the file at rest (after close), move it somewhere else, change some data using OpenXML, or perform some other operation against the file. And I have seen some convoluted workarounds to get to that point as well. Annoyed

While there is not an AfterClose event, you can create one. Hot smile The following code demonstrates it and the comments explain how/why…

private void ThisAddIn_Startup(object sender, EventArgs e)
{
    // hook to the Before Save Event
    Application.WorkbookBeforeClose += new
        Excel.AppEvents_WorkbookBeforeCloseEventHandler(
            Application_WorkbookBeforeClose);
}

/// <summary>
/// The BeforeClose event we attach to. The only event
/// exposed by Excel to us to determin that the file is
/// about to close
/// </summary>
/// <param name="Wb">The Workbook object being closed</param>
/// <param name="Cancel">If true, closing is stopped</param>
void Application_WorkbookBeforeClose(Excel.Workbook Wb,
                                     ref bool Cancel)
{
    // because this event fires before the close we will
    // do whatever work we need to do here, such as
    // prompting the user, pre-work or other items.

    // <– your code here –>
    // be sure if needed to set Cancel=true here if
    // needed…
    // <– your code here –>

    // now we check to see if cancelled…
    if (Cancel == false)
    {
        // ok – not cancelled – so we spawn a thread
        // but first…
        // we cannot use the Wb.FullName inside the delegate
        // because it will be destroyed by the time the thread
        // fires off. So we put it into a variable and pass
        // that value with the delegate.
        string wbFullPath = Wb.FullName;
        // start new inline thread
        new Thread(new ThreadStart(delegate()
        {
            // we will call back to the Handle_AfterClose
            // method and pass it the name of the workbook.
            Handle_AfterClose(wbFullPath);
            // NOTE: Because Excel takes the next action to
            // close the file, this threads immediate call
            // back into the add-in will not occur until
            // Excel has compelted the operation.
        })).Start();
    }
}

/// <summary>
/// This is the callback method ("event") we use to determine
/// the point after which our file has been closed.
/// </summary>
/// <param name="fn">The full path and name of the file
///                  closed</param>
private void Handle_AfterClose(string fn)
{
    // display a simple message at this point
    MessageBox.Show(fn + " has been closed!");
}

Opening Office Files From A Windows Form Application

A common scenario when you have a Windows form application is one in which you need to automate Office to work with or open files.

However, there are a few important details you need to about the file you are opening:

1) Is it there
2) Is it already open somewhere
3) If it is already open and can I grab the instance

To do this you must:

1) Check to see if the file exists.
2) If the file is open to grab the instance it is opened with. This can be done with BindToMoniker().
3) If the file is not open, open it and then grab an instance to it.

Here is the code:

using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using System.Diagnostics;

private Excel.Workbook openFile(string fn)
{
    // bind to the path… if the document is not open
    // then the Marshal will open an new instance of Excel or
    // open in the current active instance of Excel
    Excel.Workbook wb = (Excel.Workbook)Marshal.BindToMoniker(fn);
    if (wb == null)
    {
        // we need to start a new instance of Excel
        // with the filename passed to it
        Process p = new Process();
        p.StartInfo.FileName = "excel.exe";
        p.StartInfo.Arguments = fn; // send the filename to excel
        p.Start(); // start an instance of Excel first
        p.WaitForInputIdle();  // wait for the process to be ready
        // now bind to minker again to connect to the instance
        // of the file that we just opened
        wb = (Excel.Workbook)Marshal.BindToMoniker(fn);
    }

    // show the application – just in case
    wb.Application.Visible = true;
    // verify the window is shown
    wb.Windows[1].Visible = true;
    // if minimized – restore the window
    if (wb.Application.WindowState == Excel.XlWindowState.xlMinimized)
        wb.Application.WindowState = Excel.XlWindowState.xlNormal;
    // set the applcation to the foreground
    SetForegroundWindow(wb.Application.Hwnd);
    // then activate the workbook
    wb.Activate();
    return wb; // done
}

The code simply looks uses the Marshal BindToMoniker function to access the “object” that currently maintains the file – if it is in memory. If it is not found, we kick off a new process with that filename as a parameter passed to Excel. Excel will open the document and then we are able to use BindToMoniker again to get access to that workbook object. The code at the end does a lot of work to verify that Excel is visible and activated.

Finally to use all this:

private void openAnExcelFile()
{
    string fn = @"c:\users\davidcr\desktop\test.xlsx";
    if (File.Exists(fn))
    {
        Excel.Workbook wb = openFile(fn);
        MessageBox.Show(wb.Name + " has been opened.");
    }
}