Word VSTO before-BeforeSave event? - events

Word has several events to hook into, to control application behavior etc.
Some of these events are: Document.BeforeSave and Application.BeforeSave
As far as I know, they do the same thing - they allow you to perform actions before the new document is saved, or even cancel the save event entirely.
But in later versions of Word, there seems to be an event happening BEFORE the BeforeSave events.
It shows a "Save this file" dialog:
Once the user has chosen where to save, and click [Save], THEN is the BeforeSave event executed.
I need a way to intercept this dialog, to prevent the user from saving in certain situations.
While I can do that with the current BeforeSave event, it results in a very bad user experience, since the user first chooses where to save, only to then be told that the time spent doing that was for nothing since the document is not allowed to be saved at this time.
In earlier versions of Word this was not an issue, Word just showed the regular (simple) Save As dialog whenever the user wanted to save the new document.
Coding in VB, I've tried these two ways to handle the BeforeSave Events at Document-level and Application-level. Both fire too late.
(C# code is also welcome, it's easily translated to VB in this context.)
' --- Handle BeforeSave at Document-level
Dim vstoDoc As Document = Globals.Factory.GetVstoObject(Me.Application.ActiveDocument)
AddHandler vstoDoc.BeforeSave, AddressOf clsWord.ThisDocument_BeforeSave
Public Shared Sub ThisDocument_BeforeSave(sender As Object, e As SaveEventArgs)
' Do stuff...
End Sub
' --- Handle BeforeSave at Application-level
Private Sub HandleDocumentBeforeSaveEvent(document As Word.Document, ByRef SaveAsUI As Boolean, ByRef Cancel As Boolean) Handles _wordApplication.DocumentBeforeSave
' Do stuff...
End Sub
How do I intercept the new dialog?
Alternatively, is there another way to prevent the user from saving the document (like setting a document property or something)?

I found a way.
First part is to expand the ribbon markup to include a <commands> node above the <ribbon> element (if any).
<commands>
<command idMso="FileSave" getEnabled="FileSave_GetEnabled"/>
<command idMso="FileSaveAs" getEnabled="FileSaveAs_GetEnabled"/>
</commands>
Then add callback methods to the ribbon code file:
Public Function FileSave_GetEnabled(ribControl As Office.IRibbonControl) As Boolean
' Simple test to toggle enabled/disabled easily
' - requires ribbon invalidation for changes to take effect though
Return My.Computer.Keyboard.CapsLock
End Function
Public Function FileSaveAs_GetEnabled(ribControl As Office.IRibbonControl) As Boolean
Return FileSave_GetEnabled(ribControl)
End Function
Finally you need to handle the BeforeSave event:
Private Sub HandleDocumentBeforeSaveEvent(Doc As Word.Document, ByRef SaveAsUI As Boolean, ByRef Cancel As Boolean) Handles _wdApp.DocumentBeforeSave
If MessageBox.Show("Do you REALLY want to save the document?", "BeforeSave", MessageBoxButtons.YesNo) = DialogResult.No Then
Cancel = True
Exit Sub
End If
End Sub
This allows you to disable the "Save"-icon in the top of the ribbon, Ctrl+S and the "Save" option in the File menu.
It does not disable the "Save as" option in the file menu, but whenever a user tries to save from there, the BeforeSave event is executed, allowing you to take action there.
All in all, a better solution than previously achieved!

Related

VB6 Custom OCX integrating with another OCX Event/Method

I have created OCX in Vb6 which contains only Listview control(added from MSCOMCTL.ocx) and coded "drag and drop" functionality and currently I want to implement the OCX in another application but I'm not sure how to handle the event.
Listview has predefined Event/Method/Property, when I create my OCX the predified Lisview events are not loaded. example Listview1.Listitem
public sub Listviewocx()
eventvar1 = Data.Files.Count
For intCOunter = 1 To eventvar1
strpath = Data.Files(intCOunter)
msgbox strpath
next
end with
End sub
Thanks
Thiru
When you create ActiveX controls, you don't automatically expose the events, methods and properties of the constituent controls (in your case, the "constituent control" is the ListView). If, for example, you want a user of your control to have access to your ListView's click event, you have to raise the event again in the click event handler. Like this:
Sub ListView1_Click()
RaiseEvent "MyListViewClick"
End Sub
Then, in your application that uses your control:
Sub Listviewocx_MyListViewClick()
'Handle the event here
End Sub
You have to do similar things with properties and methods of your constituent controls.
For more information, read this and the related doc about ActiveX controls.

How To Call Button Click Event In VB6

I want to call a click in event from a button in VB6, I can't seem to figure it out. I have tried this code here but it doesn't work.
Call cmdLoads_Click(Sender, e)
I have also set the sub to public as well, still no luck.
The button sub has to have an index:
Private Sub cmdLoads_Click(index As Integer)
The name of the command button is cmdLoads, it is really cmdLoads(0). So all I had to do is change the code to this to make it work.
Call cmdLoads_Click(0)
A command button (or any other object raising events) can have multiple sinks (event subscribers) so calling "xxx_Click" sub will not notify any of the other listeners.
In short: use cmdLoads(0).Value = True as it's more portable.

How to be notified when an edit box has been updated

In a VB6 project, I would like to be notified when an edit control (text field) has been updated (i.e. : character string has been added by a user into the text field).
I did not find any documentation on the different events which can be catched for an edit MFC control.
Thanks in advance.
Its the _Change event. However, I personally do not like this event since it is notoriously unreliable.
Private Sub txtYourTextFieldControl_Change()
' some code here
End Sub
* edit *
We are talking about VB6 textbox, correct? I am not sure what MFC has to do with this.

Word VBA Application Event fires more times when more documents opened

I have the following problem. I wrote simple macro which shows MsgBox before print dialog. Here is source:
Public WithEvents App As Application
Private Sub App_DocumentBeforePrint(ByVal Doc As Document, Cancel As Boolean)
MsgBox "aaaaa"
End Sub
Private Sub Document_New()
Set App = Application
End Sub
When I open one document from template with this macro, everything is OK. But I have a problem when I open two documents from this template at same time. When I click to print button, MsgBox shown up twice. Any ideas?
Edit:
When I create document from this template and create another new document, which isnt't based on this template (both of this documents are opened at the same time) and I print from that new empty document, MsgBox shown up. This is also wrong right?
You have created application-level events that fire every time any document is being printed. They are triggered once for every document that has this code in it, so every time you print a document you will get the msgbox once for every open document that has the code in it, whether or not the document that's printing has the code in it.
So, the behaviors aren't wrong, although clearly they are not what you want.
You should put the Before_Print event in the ThisDocument module of your template. That way the event will only happen once, and only when the document being printed has the code in it.
You could put a check in your App_DocumentBeforePrint sub to check whether the instance of the application object which is firing the event is the instance that contains the active document:
If Me <> ActiveDocument Then Exit Sub

"Read" Event for Mailitem in Outlook 2007 VSTO VB.NET

Okay, this is a bit of a tricky one...
I am programming an add-in for MS Outlook 2007, using VS 2010 and VSTO, and VB.NET. My goal is to prompt the user to print off emails that they receive from certain email addresses. (This would be done with a simple MsgBox.)
Specifically, I would like the prompt the user when they are done reading the email. My concept is that it should work similarly to the "Read Receipt" functionality in Outlook. (You know, those annoying things..."The sender has requested a receipt that you have read this email blah blah")
So, the user reads the email, and then when they go to close the Inspector (or change focus to a different item if they are in Explorer view) the MsgBox pops up. I have noticed that the timing on this matches when the email becomes "read".
I have been chasing this across Google and MSDN and tutorial websites for a few days, here's what I've found:
Round 1:
The Mailitem object has an UnRead property, and it also has a PropertyChange event. I can use AddHandler for PropertyChange on every Mailitem in the Inbox, tying them into a single Subroutine that checks the argument of the PropertyChange event to make sure it's UnRead. Seems fairly workable, except that PropertyChange doesn't pass the calling object's identity, so I have no way of knowing what email in the Inbox just lost "unread" status.
In fact, none of the Mailitem events seem to pass their identity, probably because someone (MS I guess) assumes that you have a direct pointer to the Mailitem object in the first place. So this route doesn't seem to work.
Round 2:
I can grab all of the Mailitems in the Inbox into a collection, then restrict them to just the Unread ones.
Dim inbox As Outlook.MAPIFolder = Nothing
Dim WithEvents unreadItems As Outlook.Items
inbox = Me.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox)
unreadItems = inbox.Items.Restrict("[Unread]=true")
Since unreadItems was Dimmed WithEvents, I could write a Sub to Handle unreadItems.ItemRemove, which would work fine. But the ItemRemove event has no object argument! Doh!
Round 3:
Let's say I do the opposite: I get the inbox contents and restrict to [Unread]=false, then use the .ItemAdd event. This would work to a degree, except that it would theoretically call whenever an "[Unread]=false" item was dumped into the Inbox by the user from any folder, not just a change from the Unread to Read group within the Inbox. So, unless I'm misunderstanding something here, also not an option.
Round 4:
Now, something I also thought of was simply Dimming the Inbox items collection WithEvents and going off the .ItemChange event, but this event doesn't really specify what changed about the object, so no dice.
In closing, I'm pretty darn stumped with this. I am very close to backing down from my goal. A lame alternative is to alert the user when they receive an email from one of the designated addresses (because I believe the Application.NewMail event won't give me any hassle). But then, I will have to simply alert the user--I won't prompt them to print an email they haven't even read yet.
This alternative is undesirable and I figured that I would present my problem for inspection and suggestion.
-Matt
P.S. I'm coming from making iPad apps with Objective-C, where I'm building most of the object hierarchy myself...it's weird to have to deal with COM objects that have such limitations.
I'm not quite sure how you want your UI to behave, because it's not quite clear when the user is done reading their email. One way to look at it is that they are done when they have looked at it, meaning that the inspector window has shown the mail and the user is switching to another one. To catch that, you would probably be best off watching events from the inspector, not the mail items.
The other way to look at it is that a mail is read whenever it is marked as Read. Be aware that the user may actually turn off the option to mark items as read automatically! This can be done in Tools->Options->Other->Reading pane, like this:
Also, the user may select items and mark them as read manually, so you need to think about what you want do in that case.
If you want to watch for the change in "read" property of the MailItem, you are very close in round 1. The thing you need to add is that you shouldn't tie all of your handlers to a single subroutine in a single object instance. Instead, you can create your own class, something like this (C# code):
class ItemWatcher
{
// The Outlook mailitem to watch for
private Microsoft.Office.Interop.Outlook.MailItem itemBeingWatched = null;
public ItemWatcher(Microsoft.Office.Interop.Outlook.MailItem item, Form1 parentForm)
{
itemBeingWatched = item;
itemBeingWatched.PropertyChange += new Microsoft.Office.Interop.Outlook.ItemEvents_10_PropertyChangeEventHandler(itemBeingWatched_PropertyChange);
}
void itemBeingWatched_PropertyChange(string Name)
{
// Respond to property <Name> in the object itemBeingWatched having changed
}
}
Then you need to create a collection of your ItemWatcher classes and set them to watch your emails.
Note that you will also need to watch for items you need to add/remove from your collection of watched items, when a new mail arrives or an old mail is deleted/moved.

Resources