Connecting Excel to Outlook - excel

I am trying to write a function which will tell if Excel VBA has a reference set to Outlook:
Function RefOutlook() As Boolean
Application.Volatile
On Error Resume Next
If olFolderInbox=6 Then
RefOutlook = True
Else
RefOutlook = False
End If
End Function
My logic is that if a reference is established, VBA will recognise an Outlook built-in constant olFolderInbox (equal to 6) and will return True, otherwise there will be an error and the function shall return False. The problem is that if there is no reference, the error is not suppressed, an error message comes up and the function is not working.
Many thanks in advance for your feedback and suggestions.

What you really need is to specify Option Strict Statement in the beginning of VBA modules. Restricts implicit data type conversions to only widening conversions, disallows late binding, and disallows implicit typing that results in an Object type.
When Option Strict On or Option Strict appears in a file, the following conditions cause a compile-time error:
Implicit narrowing conversions
Late binding
Implicit typing that results in an Object type
So, you will get a compile-time error if the Outlook reference is not added.

I would suggest that you use early binding for development, and convert to late binding when deploying your application. By doing so, you not only avoid the problem of constants not being available, but also any versioning issues.
When you do so, you will need to replace any constants, such as olFolderInbox, with your own global constants:
Global Const OL_FOLDER_INBOX = 1
Regards,

Related

Confusion in creating excel application object using VB.net: CreateObject vs New

I am facing difficulty in understanding the following concepts. I had posted a question some time back - read through the answers but some things are still not clear. I state my confusion below:
My first question refers to the following code piece
Option Strict On
Imports Microsoft.Office.Interop
Dim oxl As Excel.Application
oxl = CreateObject("Excel.Application")
In the above code piece, the statement oxl = CreateObject("Excel.Application") throws an error stating, Option Strict On disallows implicit conversions from Object to Application. My question is I read from many sources that it is always better to keep Option Strict ON but in this case when we need to create a new excel application, the Option Strict ON is preventing us from doing so. So what is the best practice that should be followed for such a conflict?
Next I tried replacing the statement oxl = CreateObject("Excel.Application") with oxl = New Excel.Application. It was observed that even with Option Strict ON, we can create a new excel application object with the NEW keyword. It was also checked with GetType that in both cases that is, using CreateObject and NEW, the type of object being created was: System._ComObject.So my question is if the type of object being created remains remains the same, why is that Option Strict disallows CreateObject but allows the creation of the excel application object using NEW?
To study it further, I extended the above code to the following:
Option Strict On
Imports System
Imports Microsoft.Office.Interop
Module Program
Dim oxl As Excel.Application
Dim owb As Excel.Workbook
Dim osheet As Excel.Worksheet
Sub Main()
oxl = New Excel.Application
'oxl = CreateObject("Excel.Application")
Console.WriteLine(oxl.GetType)
oxl.Visible = True
owb = oxl.Workbooks.Add()
osheet = owb.Worksheets("Sheet1") ‘Error: Option Strict ON disallows implicit conversions from ‘Object’ to ‘Worksheet’
osheet.Range("A1").Value = 53
Console.WriteLine("Hello World!")
Console.ReadLine()
End Sub
End Module
When we run the code we see that the error Option Strict ON disallows implicit conversions from ‘Object’ to ‘Worksheet’ comes at the line: osheet = owb.Worksheets("Sheet1")
Question:
Why is the error coming? I mean if, owb = oxl.Workbooks.Add()can work (that it returns a workbook which is referred to by owb) then why is osheet = owb.Worksheets("Sheet1") not working because the right hand side returns the “Sheet1” of the workbook which osheet should be able to point to (given that it is of the type Excel.Worksheet)?
Sometimes it just doesn't have the information that something is more specific than an Object. If you don't use the default property and use Item instead, as in owb.Worksheets.Item("Sheet1"), you can hover over the Worksheets part to see that represents the .Sheets, but hovering over the Item part reveals it has no details of the items therein - it says it returns an Object.
You know what it should be, so if you had
Imports XL = Microsoft.Office.Interop.Excel
then you could do
osheet = DirectCast(owb.Worksheets("Sheet1"), XL.Worksheet)
and the types would all work out.
This is what VB statements about COM objects actually do.
Information for Visual Basic Programmers
Visual Basic provides full support for Automation. The following table
lists how Visual Basic statements translate into OLE APIs.
Visual Basic statement OLE APIs
CreateObject (“ProgID”)
CLSIDFromProgID
CoCreateInstance
QueryInterface to get IDispatch interface.
GetObject (“filename”, “ProgID”)
CLSIDFromProgID
CoCreateInstance
QueryInterface for IPersistFile interface.
Load on IPersistFile interface.
QueryInterface to get IDispatch interface.
GetObject (“filename”)
CreateBindCtx creates the bind context for the subsequent functions.
MkParseDisplayName returns a moniker handle for BindMoniker.
BindMoniker returns a pointer to the IDispatch interface.
Release on moniker handle.
Release on context.
GetObject (“ProgID”)
CLSIDFromProgID
GetActiveObject on class ID.
QueryInterface to get IDispatch interface.
Dim x As New interface
Find CLSID for interface.
CoCreateInstance
QueryInterface
A standard COM VTable the first three entries are IUnknown
AddRef, Release (decreases the ref count), and QueryInterface to find what interfaces this object support.
The next four entries are IDispatch
GetIDsOfNames, Invoke , GetTypeInfoCount, GetTypeInfo.
The entries after that are your methods and properties, and all are a indirect function call.
To get the code in memory you use the COM API calls, such as CoCreateInstance
You need to decide to early or late bind. Early binding requires the program to be installed so its type library can be read so it types are compiled into the program. Late binding doesn't care about compile time. There is a conversation Hello object, do you have a function called x. Object replies Yes, it is function 7, Can you please do function 7 object. Early binding function 7 is hard coded. You can only late bind to generic objects. –
So a COM object is 4 x 32 bit. One is the reference count, one is the address if the Virtual Function Table (VTable), 2 are unused. In early binding to call a function 7 the compiler does Address_Of_Vtable + (4 x 7) (being 4 bytes for an address). See IDispatch. N.B. Microsoft.Office.Interop is not used at all in late binding. –
Only the generic object can be used in late binding and cannot be used in early binding. Early binding requires you to tell it the specific object. You are mixing and matching. The compiler is confused, just like you. –
Option Strict Restricts implicit data type conversions to only widening conversions, disallows late binding, and disallows implicit typing that results in an Object type. learn.microsoft.com/en-us/dotnet/visual-basic/… –

How can I get the range of a binding in Office.JS?

There used to be binding.range or binding.getRange(), but both do not function for a binding, and nothing similar is documented on https://learn.microsoft.com/en-us/javascript/api/office/office.binding. There is only the possibility to get the data of the according cell. Did this function go away and is it coming back or what is the issue here? I need it for an Excel AddIn.
Well, the following code from https://learn.microsoft.com/en-us/javascript/api/excel/excel.binding?view=excel-js-preview#getRange__ should work:
var binding = ctx.workbook.bindings.getItemAt(0);
var range = binding.getRange();
range.load('cellCount');
return ctx.sync().then(function() {
console.log(range.cellCount);
});
But in my case, with a different code snippet (got the binding on another way), getRange() did not work, so there might be differences of how you got hands on the binding. But at least it should be possible. (I am not examining this further as I found a way not needing this functionality for my use case, cf. How do I use Office.JS to add invisible information to a cell in Excel?)

How can I downcast an object using only late-binding in VBA?

I am writing a VBA application, and for a specific function, I am using only late-binding, as most of the users of the application won't have the reference installed (and won't use this specific function).
The object I am using behaves like:
class PISDK{
PIServer GetServer(string hostName)
}
The GetServer method returns a PIServer object, but a more specific interface exists, implementing PIServer:
interface IGetPoints2 : PIServer{}
I would like to downcast the PIServer object to a IGetPoints2 object.
Without doing anything, I get a PIServer object:
Dim PiSdk As Object
Dim PiServer As Object
Set PiSdk = CreateObject("PISDK.PISDK")
Set PiServer = PiSdk.GetServer("foo")
Looking at PiServer in the debugger confirms that.
Using a strongly typed variable should work, but I do not want to reference any of the types used here.
How can I downcast this object using late-binding only?
Please read this:
As you are not adding a reference to the PI SDK Type Library, I
believe you cannot use "rtInterpolated" as the second parameter of
the ArcValue method; instead, you can use the corresponding number
(which is 3 for "rtInterpolated" in the RetrievalTypeConstants
enumeration).
VBA with late binding is tricky with optional
parameters, as we cannot omit them when calling a method. Instead,
you need to use either Nothing (in case the optional parameter is
an object) or "" (in case the optional parameter is a string) as
"parameter placeholders" (by the way, the same happens in
VBScript, a scripting language for scripts contained in files with
.vbs extension that run independently from any application).
able to put some functional sample code together, which is more
complete and will hopefully help you.

Excel VBA 438 Error Raised on PC but not on Mac

I have VBA code which adds to a custom Edge class object to a dictionary of dictionaries. I am using this Dictionary class.
This works as expected on Mac, but when I try to run my spreadsheet command on a PC I get a 438: Object doesn't support this property or method error.
The line of code raising the error is edges_dict(user)(Provider) = created_edge where edges_dict(user) is a dictionary, Provider a String, and creaded_edge an Edge.
If I instead use edges_dict(user).Add Provider, created_edge everything works well, but I want the overwrite ability the first call provides. Additionally, edges_dict(key) = value works. The issue seems to arise from my nesting.
Here is the code where I create the dictionary of dictionaries:
Public edges_dict As New Dictionary 'Stores in degrees
Public s_array() As String
Public single_node As Dictionary 'Dictionary keyed by source node holding in degree edges for a certain node
Sub Generate_Matrix()
'Code to populate s_array() here
'Populate dictionary with key as node, value as array of inbound edges to be filled
Set single_node = New Dictionary
edges_dict.Add s_array(I), single_node
Next I
End Sub
edges_dict(user)(Provider) = created_edge
There's a lot going on, crammed into that little instruction. If either key lookup fails, how do you know which key failed? You don't, and you can't know.
Split it up, there's no use trying to compact as much functionality as possible into a single instruction.
Dim providers As Dictionary
Set providers = edges_dict.Item(user)
As a bonus, you get IntelliSense and early binding!
creaded_edge an Edge.
Assuming an Edge is an object, the bug isn't with the Win32 code blowing up with error 438 - the bug is with the Mac code "working" when it should absolutely be complaining about the missing default member on the Edge class.
The error is indirectly telling you that you need a Set keyword to assign that object reference:
Set providers(provider) = created_edge
Without the Set keyword, you are let-coercing the created_edge object, which means the dictionary item associated to that key is NOT an object on the "working" Mac code, but whatever data type the class' default member is... assuming it has a default member (and it doesn't... so it's unclear how/why the Mac code isn't blowing up) - here's your "nested" instruction, with the implicit code in square brackets:
[Let] edges_dict[.Item](user)[.Item](Provider) = created_edge[.DefaultMember]
The reason why .Add works in both cases, is because no let-coercion is happening in that case: the object reference itself is being added as a value.
Error 438 is being thrown because let-coercing an object that doesn't have a default member (as seems to be the case), is supposed to fail.
Adding Set in front of the assignment would fix it... but does not behave the same as .Add: .Add will throw a duplicate key error if you try to re-add an existing key, while assigning the keyed value like you did will silently replace the value associated to an existing key.

Excel add-in with logging class: How to react to VBA state loss?

The setup
I'm developing and maintaining an Excel add-in that comes with its own tab of controls within Excel's Ribbon UI. I've come across the problem of state loss before (meaning loss of all variables with global scope, static variables, etc, which of course includes my reference to the RibbonUI). With regards to the ribbon reference I've "solved" the problem by including a "Reset Ribbon" button that restores the reference from a persistently stored pointer and then invalidates the ribbon. Although certainly not the most elegant, this part works just fine.
However, after the introduction of a logging class, the state loss issue haunts me once again. The logger is instantiated in ThisWorkbook's module:
Private Sub Workbook_Open()
Set LogToFile = SingletonFactory.getToFileLogger
End Sub
and is then put to work, for example, as follows:
Private Sub buttonReloadObjects_onAction(ByVal control As IRibbonControl)
LogToFile.trace "Event firing: buttonReloadObjects_onAction"
' more stuff happening...
invalidateRibbon ' restores ribbon object and invalidates it
End Sub
The logger is instantiated when the add-in is loaded so that I have the freedom to log whatever I want within the confines of my add-in's code. It has several logging levels like trace/debug/error/... and a couple of other methods. Usually it works just fine - until the state loss hits (usually caused by an unrelated error, followed by clicking "End").
State loss
At this point the VBA environment forgets about the very existence of my LogToFile object and nothing works any more, because every click on the ribbon controls will trigger a runtime error 91: Object variable or with block variable not set pointing to whatever line is the first to contain a reference to LogToFile.
A solution?
Now, short of doing crazy workarounds like placing
if not isObject(LogToFile) then
Set LogToFile = SingletonFactory.getToFileLogger
end if
LogToFile.trace "Message"
before any occurrence of LogToFile, the only real "solution" I was able to come up with is to wrap all my logger calls in functions (residing in a standard module) and call these functions any time I want to send something to the log. This way I could catch the missing object reference right before the object is needed and I avoid calling methods of uninstantiated objects.
However, after having everything neatly encapsulated in class modules, it strikes me as odd, maybe even wrong(?), going down this route.
So, is there a "proper" solution to the problem of a lost logger instance? Or is my suggested approach already as proper as it can get?
Note: This problem is of course not specific to logging classes. It affects all global variables, most notably my ApplicationEventClass. The issue just happens to be the most glaring with the logger due to its frequent usage around all entry points to the code.
You only need one function that either returns the original variable or resets it. If you call that function LogToFile you don't need to change any of the other code other than removing the Workbook_Open code which is then superfluous. So:
Function LogToFile() As WhateverVariableType
Static temp as WhateverVariableType
If temp is Nothing then Set temp = SingletonFactory.getToFileLogger
Set LogToFile = temp
End Function
This way you will also still benefit from Intellisense when writing the code.
Note: you may not actually need the temp variable - it depends on whether there are settings that you want persisted. If there are, you may want to reset them in the function too.

Resources