Outlook VSTO: Cancel out of Send event for appointment items - outlook

I am creating an Outlook VSTO that allows users to enter information in an additional form-region on the New appointment form.
When sending the appointment I would like to capture the 'Send' event and perform some checks on the data the user provided. When this data is OK the appointment can be send, otherwise the Send action must be cancelled.
My code is like this:
Dim apptItem as Outlook.AppointmentItem
Private Sub Test_FormRegionShowing(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.FormRegionShowing
apptItem = OutlookItem
AddHandler apptItem.Send, AddressOf sendAppt
End Sub
Private Sub sendAppt()
If some_test = False then
else
Cancel=True
End If
End Sub
How do I pass the event arguments to my SendAppt function so that I can cancel out of the routine and prevent the meeting from being send?

The appointment itself is never sent - Outlook creates a brand new MeetingItem object and send it. You need to use the Application.ItemSend event and check if the Item parameter points to a MeetingItem object. You can then check which appointment it corresponds to using MeetingItem.GetAssociatedAppointment.

Well, the answer was rather simple and the solution is like this:
Private sub SendAppt (ByRef Cancel as Boolean)
If [some test] = False Then
Cancel = True
End If
End Sub
This will cancel out of the send routine and holds the meeting item from being send.

Related

Invalid use of AddressOf operator VB6

My first post here.
I have c# .dll, to use with a scanner.
I intend to use it with some legacy vb6 applications.
The .dll raises an event with the scanned code using RaiseArgs.
I am trying to write an .ocx library for use with VB6 apps.
To catch this event in an .ocx library I am trying to adapt this code:
Sub TestEvents()
Dim Obj As New Class1
' Associate an event handler with an event.
AddHandler Obj.Ev_Event, AddressOf EventHandler
' Call the method to raise the event.
Obj.CauseSomeEvent()
' Stop handling events.
RemoveHandler Obj.Ev_Event, AddressOf EventHandler
' This event will not be handled.
Obj.CauseSomeEvent()
End Sub
Sub EventHandler()
' Handle the event.
MsgBox("EventHandler caught event.")
End Sub
Public Class Class1
' Declare an event.
Public Event Ev_Event()
Sub CauseSomeEvent()
' Raise an event.
RaiseEvent Ev_Event()
End Sub
End Class
But I am getting
Invalid use of AddressOf operator error when I call: AddHandler Obj.Ev_Event, AddressOf EventHandler
What could be probable cause of this error?
I suspect, that i am not going in the right direction with solving this task, so would it be a better way to approach this problem?
You have a mix of VB6 and VB.NET I think. In VB6, if you want to add an event handler for event on an object that you create yourself (as opposed to being added with the Forms designer) you would do it something like this:
Private WithEvents mObjectThatHasEvents As Class1
Private Sub StartListeningToEvents()
If mObjectThatHasEvents Is Nothing Then
Set mObjectThatHasEvents = New Class1
End If
End Sub
Private Sub TriggerEvent()
mObjectThatHasEvents.CauseSomeEvent
End Sub
'Assumes that Class1 has an event called "MyEvent".
Private Sub mObjectThatHasEvents_MyEvent()
MsgBox("EventHandler caught event.")
End Sub
Private Sub StopListeningToEvents()
Set mObjectThatHasEvents = Nothing
End Sub
If you need to keep the event source object alive while not listening to its events, you will need a second reference to it via a variable that is not "WithEvents".
I should point out that the VB6 IDE will recognize the event source. In the code pane, use the dropdowns at the top to select the object and event. The IDE will stub them out for you automatically (just like buttons or anything else).
tcarvin pretty much explained what you have to do but here's your code, adjusted:
Private WithEvents mObjWithEvents As Class1
Sub TestEvents()
Dim Obj As New Class1
Set mObjWithEvents = Obj
' Call the method to raise the event.
Obj.CauseSomeEvent
' Stop handling events.
Set mObjWithEvents = Nothing
' This event will not be handled.
Obj.CauseSomeEvent
End Sub
Private Sub mObjWithEvents_EvEvent()
' Handle the event.
MsgBox ("EventHandler caught event.")
End Sub
And your Class1:
Public Event EvEvent()
Public Sub CauseSomeEvent()
' Raise an event.
RaiseEvent EvEvent
End Sub
Notice that Ev_Event was renamed to EvEvent.

Passing timer value to next form

I developing an application that require pass the timer value to next form. For example Form A time out is 30 seconds, If the user dont click on the screen, it will back to the Main Screen. Sames go to the Form B. Please help. Thanks
You need to modify the constructor(s) of the page(s) you want to accept the timer value, like this:
Public Class FormA Inherits Form
Private timerValue As Integer
Public Sub New(_timerValue As Integer)
timerValue = _timerValue
End Sub
Public Sub MethodA
' Do something with timerValue here
End Sub
End Class

How to raise an event from vb6 module?

I have developed a custom visual basic 6 control and declared a few custom events. Is it possible in vb6 to raise these events from a module or I need to implement a special "proxy" methods in my control to do this?
RaiseEvent:
Compile error:
Only valid in object module.
(Which makes sense.)
Yes, you need a Friend method on your class that you would call to raise events from your module:
Class:
Public Event Click()
Friend Sub OnClick()
RaiseEvent Click
End Sub
Module:
someVar.OnClick
Perhaps not entirely the answer you are looking for, but it is possible to use Event-like procedures from plain Modules:
First define a Callback Interface:
IEventsClient (Class Module):
Option Explicit
Public Sub PropertyChanged(sender As Object, property As String)
End Sub
MyModule:
Option Explicit
Public EventClients As Collection
Public Sub OnPropertyChanged(property As String)
Dim eventsClient As IEventsClient
Dim element As Variant
For Each element In EventClients
Set eventsClient = element
eventsClient.PropertyChanged MyControl, property
Next
End Sub
Public Sub RaiseSomePropertyChanged()
OnPropertyChanged "SomeProperty"
End Sub
The main Form:
Option Explicit
Implements IEventsClient
Private Sub Form_Load()
'Entry point of the application'
Set MyModule.EventClients = New Collection
MyModule.EventClients.Add Me
End Sub
Private Sub IEventsClient_PropertyChanged(sender As Object, property As String)
If TypeOf sender Is MyControl Then
Select Case property
Case "SomeProperty"
' DoSomething'
End Select
End If
End Sub

Validate a XDocument against schema without the ValidationEventHandler (for use in a HTTP handler)

(I am new to Schema validation)
Regarding the following method,
System.Xml.Schema.Extensions.Validate(
ByVal source As System.Xml.Linq.XDocument,
ByVal schemas As System.Xml.Schema.XmlSchemaSet,
ByVal validationEventHandler As System.Xml.Schema.ValidationEventHandler,
ByVal addSchemaInfo As Boolean)
I am using it as follows inside a IHttpHandler -
Try
Dim xsd As XmlReader = XmlReader.Create(context.Server.MapPath("~/App_Data/MySchema.xsd"))
Dim schemas As New XmlSchemaSet() : schemas.Add("myNameSpace", xsd) : xsd.Close()
myXDoxumentOdj.Validate(schemas, Function(s As Object, e As ValidationEventArgs) SchemaError(s, e, context), True)
Catch ex1 As Threading.ThreadAbortException
'manage schema error'
Return
Catch ex As Exception
'manage other errors'
End Try
The handler-
Function SchemaError(ByVal s As Object, ByVal e As ValidationEventArgs, ByVal c As HttpContext) As Object
If c Is Nothing Then c = HttpContext.Current
If c IsNot Nothing Then
HttpContext.Current.Response.Write(e.Message)
HttpContext.Current.Response.End()
End If
Return New Object()
End Function
This is working fine for me at present but looks very weak. I do get errors when I feed it bad XML. But i want to implement it in a more elegant way. This looks like it would break for large XML etc.
Is there some way to validate without the handler so that I get the document validated in one go and then deal with errors?
To me it looks Async such that the call to Validate() would pass and some non deterministic time later the handler would get called with the result/errors. Is that right?
Thanks and sorry for any goofy mistakes :).
I have been working with the above code for some time now and I was incorrect about it being Async. It does not move to the next statement before the whole document is validated.

VB6 Collection Remove Doesn't Fire Class_Terminate

I apologize in advance; this is a long question. I've tried to simplify as much as I can but it's still a bit more long-winded than I'd care to see.
In some legacy code, we've got a VB6 collection. This collection adds objects via the .Add method and removes them via the .Remove method. However, via tracing I can see that sometimes when the .Remove is called it appears that the class terminate for the object isn't called. But it's not consistent; it happens only rarely and I can't isolate the circumstances under which it fails to fire the class terminate.
Consider the following demonstration code:
Option Explicit
Private Const maxServants As Integer = 15
Private Const className As String = "Master"
Private Sub Class_Initialize()
Debug.Print className & " class constructor "
Set g_coll1 = New Collection
Dim i As Integer
For i = 1 To maxServants
Dim m_servant As Servant
Set m_servant = New Servant
m_servant.InstanceNo = i
g_coll1.Add Item:=m_servant, Key:=CStr(i)
Debug.Print "Adding servant " & m_servant.InstanceNo
Next
End Sub
Private Sub Class_Terminate()
Dim i As Integer
For i = maxServants To 1 Step -1
g_coll1.Remove (CStr(i))
Next
Debug.Print className & " class terminator "
Set g_coll1 = Nothing
Exit Sub
End Sub
and
Option Explicit
Private Const className As String = "Servant"
Private m_instanceNo As Integer
Private Sub Class_Initialize()
m_instanceNo = 0
Debug.Print className & " class constructor "
End Sub
Public Property Get InstanceNo() As Integer
InstanceNo = m_instanceNo
End Property
Public Property Let InstanceNo(newInstanceNo As Integer)
m_instanceNo = newInstanceNo
End Property
Private Sub Class_Terminate()
Debug.Print className & " class terminator for " & CStr(Me.InstanceNo)
End Sub
and this is the test harness code:
Option Explicit
Global g_coll1 As Collection
Public Sub Main()
Dim a As Master
Set a = New Master
End Sub
Now, for every run, the class_terminate of Servant is always invoked. And I can't see anything in the production code which should keep the object in the collection referenced.
1.) Is there any way to force the class terminate on the Remove? That is, can I call Obj.Class_Terminate and be assured it will work every time?
2.) On my production code (and my little test app) the classes are marked "Instancing - 5 MultiUse". I realize this may be some sort of threading issue; is there an effective way to prove (or disprove) that multi-threading is the cause of this issue--some sort of tracing I might add or some other sort of test I might perform?
EDIT: Per MarkJ's insightful comment below, I should add that the test posted above and the production code are both ActiveX exe's--part of the reason I ask about mulit-threading.
We had a similar issue, but where we could trace the non-termination of the objects to be down to an instance being held elsewhere in our application.
In the end, we had to write our Termination method like this:
Private Sub Class_Terminate()
Terminate
End Sub
Public Sub Terminate()
'Do real termination in here'
End Sub
So whenever you really wanted the class to be terminated (i.e. when you call g_coll1.Remove), you can also call Terminate on the held object.
I think that you could also make Class_Terminate public, but that's a bit ugly in my opinion.
Re your point (2), I think it's very unlikely to be a threading issue, but I can't think of a good proof/test off the top of my head. I suppose one very quite thing you can consider is: do you manually use threading in your application? VB6 doesn't do much threading automatically... (see edit below)
[Edit] MarkJ tells us that apparently building as an ActiveX application means that VB6 does automatically do threading. Someone else will have to explore the implications of this, since I wasn't familiar with it!

Resources