VBA and properly handling Macro Security Level - vba

Q: What is recommended way of notifing user about loss of functionality unless User change macro security settings?
What I do now:
I display warning on the first sheet user see after opening Workbook, with explanation why things WONT work unless proper settings are set.
And I hide it on start up. (Which wont happen unless settings are OK)
But its not perfect solution:
That message is just one time. (While user could send that sheet to somebody else with different settings...)
Hiding and showing those few rows is treaded by Excel as changing document. (So just opening and closing excel will generate Save changed warning!)

Ok, here is best answer I could think off
(And big thx to #brettdj for suggestion!)
Sub HideMacroSecWarning()
Status = ThisWorkbook.Saved
Help.Range("Warning").rows.hidden = True
ThisWorkbook.Saved = Status
End Sub
Sub ShowMacroSecWarning()
Status = ThisWorkbook.Saved
Help.Range("Warning").rows.hidden = False
ThisWorkbook.Saved = Status
End Sub
Those are for showing and hiding macros. Saved state is preserved so only user actions can trigger "unsaved changes" dialogbox.
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Call ShowMacroSecWarning
End Sub
Private Sub Workbook_Open()
Call HideMacroSecWarning
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Call ShowMacroSecWarning
End Sub
Private Sub Workbook_AfterSave(ByVal Success As Boolean)
Call HideMacroSecWarning
End Sub
Those assure that no matter the state of user settings warning will ALWAYS be displayed in SAVED FILE. (So even if for user Exel do not display that warning, somebody else who will open it on different machine will see that warning.)

Related

How to prevent any changes in code and be able at same time to look at it? (VBA)

Is there, by chance, any way to prevent from changing code and simultaneously be able to look at it?
The purpose is introductory, so that user could look at code without ability to do any changes.
Thank you in advance
Here is a tricky one. You can add the following three subscripts and it will make the file Read Only and also stop anyone from saving the workbook unless they use the SaveForReal Subscript/Macro.
Inside the ThisWorkbook VBA Object:
Private Sub Workbook_Open()
Application.DisplayAlerts = False
ActiveWorkbook.ChangeFileAccess Mode:=xlReadOnly
Application.DisplayAlerts = True
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
ThisWorkbook.Saved = True
Cancel = True
End Sub
Inside a Module Object:
Private Sub SaveForReal()
Application.EnableEvents = False
ThisWorkbook.Save
Application.EnableEvents = True
End Sub
To save the workbook, you need to open the VBA editor and Run the SaveForReal Subscript, otherwise the Save Button and Save As button does nothing.
Edit: Added On Open Read Only Change.

Make Excel visible when closing userform

I have a button in an Excel worksheet that opens a userform when clicked. It also hides the worksheet, making it so you can only see the userform. Here's what I have to make that happen. This works fine.
Sub Button2_Click()
Application.Visible = False
frmDataColl.Show
End Sub
Then I have this code that isn't working the way I want it to. Upon clicking the X to close the userform, I want the userform to close and also make Excel visible again. But this only closes the userform. It doesn't bring the workbook back into view.
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
Application.Visible = True
End If
End Sub
Get rid of the Queryclose event and change your code to:
Sub Button2_Click()
Application.Visible = False
frmDataColl.Show
Application.Visible = True
End Sub
The form has no business knowing about Application.Visible state. As this answer shows, Button2 set it to False, it's Button2's job to set it back to True.
Your code works perfectly fine here - the problem is likely in code you haven't posted. Keep procedures' responsibilities well defined and you won't have these issues.
Another common trap you've fallen into (as did the other answer), is to work with a stateful default instance. Forms are objects - treat them as such!
Application.Visible = False
With New frmDataCol1
.Show
' here you still have access to the form's state!
End With
Application.Visible = True
Now, if you don't handle QueryClose, that red "X" button effectively destroys the object and, normally, you absolutely don't want that.
What you want, is to control the object's lifetime yourself - that's what you have that With block for!
So you do handle QueryClose, but only to say "Hide the form instance, don't destroy it!" - like this:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
Cancel = True
Me.Hide
End If
End Sub
That way when your dialog gets closed, the calling code can still access the public state and act accordingly. If you worked off the default instance, letting QueryClose destroy the instance causes the state to be lost. And since it's a default instance, a brand new one gets created as soon as you query that state, making the bug extremely difficult to find... if you don't know how objects with a default instance work.

VBA for Word: Add sub on event

I want to add this code to ThisDocument in my Word .docm file.
Sub AutoClose()
ActiveDocument.Saved = False
End Sub
The code prevents the "Do you want to save?" dialog from appearing. I only want to add this however after a certain click event in the document, because I also have Save As and Save disabled, and if I add all three, then I wouldn't be able to save the document myself. (In fact, I can't add all three, for that reason.)
If I add the above code only to be active after the last click event in the document however it should be fine, since then I can still save changes I make, as long as I haven't clicked that trigger at the end yet. Preventing people from closing and getting the "Do you want to save?" dialog at the end is the most important to me.
is there a way to write the above code to ThisDocument when another click event fires? Or put another way, is there any way to have a click event put the above code into effect?
Here's the sub at the end that I'd want to have trigger the above code to (activate? Be written? Be enabled? Be uncommented?)
Private Sub formatSaveB_Click()
ActiveDocument.Bookmarks("mark3").Select
Selection.Delete
ActiveDocument.InlineShapes(1).Delete
With Dialogs(wdDialogFileSaveAs)
.Format = wdFormatFilteredHTML
.Show
End With
End Sub
So after that event happens, I want
Sub AutoClose()
ActiveDocument.Saved = False
End Sub
to be active in ThisDocument -- but not before that event.
Programatic access to the VBA project will need to be enabled if it isn't already. However, to add the sub you described after the other code runs add this code to your sub:
ThisDocument.VBProject.VBComponents("ThisDocument").CodeModule.AddFromString "Sub AutoClose(): ActiveDocument.Saved = False: End Sub"
That should do the trick. You may have to tinker a little, I can't test fully because I'm in an environment where programatic access to the VBA project is disabled, but that should do what it sounds like you want to do.
Slight edit: If you want to prevent the "Do you want to save changes you made ... " message when closing word you would need to do this:
ThisDocument.VBProject.VBComponents("ThisDocument").CodeModule.AddFromString "Private Sub Document_Close(): ActiveDocument.Saved = True: End Sub"
Which will prevent that message from displaying when closing the document.
I can't completely follow your logic but I think what you're asking is something like this:
ThisDocument module
Public event1 As Boolean
Public event2 As Boolean
Sub AutoOpen()
event1 = False
event2 = False
End Sub
Event Procedures
Private Sub formatSaveB_Click()
'// Event code here...
event1 = True
End Sub
Private Sub otherEvent_Click()
'// Event code here...
event2 = True
End Sub
Then in your final sub
Sub AutoClose()
'// Saved state only set to true if previous 2 events have been executed.
ActiveDocument.Saved = (event1 And event2)
End Sub
Set a flag in the event handler you want to disable the dialog, then test it when you exit. Note that you have to check for either the flag or if the document is already saved to avoid being prompted if the document actually has been saved but you haven't triggered formatSaveB_Click(). Assumes that formatSaveB_Click() is in ThisDocument.
In ThisDocument:
Private clicked As Boolean
Sub AutoClose()
ActiveDocument.Saved = clicked Or ActiveDocument.Saved
End Sub
Private Sub formatSaveB_Click()
ActiveDocument.Bookmarks("mark3").Select
Selection.Delete
ActiveDocument.InlineShapes(1).Delete
With Dialogs(wdDialogFileSaveAs)
.Format = wdFormatFilteredHTML
.Show
End With
'Set your flag here.
clicked = True
End Sub
Okay adding this line from MattB did the trick, after I discovered that his suggestion had a typo, and I deleted the extra letter :)
ThisDocument.VBProject.VBCompontents("ThisDocument").CodeModule.AddFromString "Private Sub Document_Close(): ActiveDocument.Saved = True: End Sub"
I was just pasting it in to try it like the others, and it had VBCompontents which I didn't spot until just now going over this again, and once I deleted the extra t, bingo.
Just what I needed. Thanks a bunch.

ActiveX control Blocks Me.Saved=True

Previously I have used this:
Private Sub Document_Close()
Me.Saved = True
End Sub
to disable the save prompt when exiting a Word document and changes have been made, however I have added a "Combo Box (ActiveX Control)", and now Word is prompting to save again. Is there a way around this?
I've tried writing code to just delete the Combo Box when the document closes, but the box deletes itself after being used (not my code, it just does), then when the document closes the box doesn't exist and causes an error there. I could just do an if exist/error control, but I feel like that is just getting sloppy instead of finding the root problem... can someone help?
If had the same issue, and found the solution with Bing:
http://answers.microsoft.com/en-us/office/forum/office_2007-customize/activex-control-blocks-ms-word-vba/71eca664-8e43-4e4f-84c5-59154661ee5a
The following code helped me to get around this issue:
Dim WithEvents App As Application
Private Sub App_DocumentBeforeClose(ByVal Doc As Document, Cancel As Boolean)
'Did the user saved our file?
If Not ThisDocument.Saved Then
'Close our file?
If Doc.Name = ThisDocument.Name Then
'Cancel the dialog always!
Cancel = True
'Set the save state
ThisDocument.Saved = True
'Close the document after this event
Application.OnTime Now + TimeSerial(0, 0, 1), Me.CodeName & ".Document_AfterClose"
End If
End If
End Sub
Sub Document_AfterClose()
'Close the document without saving
ThisDocument.Close False
End Sub
Private Sub cboEquip_Change()
'Let the document know that a change is made
ThisDocument.Saved = False
End Sub
Private Sub Document_Open()
Set App = Application
End Sub

Closing a bound form without saving changes

Quick question here, hoping for a concise sensible solution.
I have a bound form purely for data entry (cannot browse records, only insert them).
I will have a lot of users that screw up. To avoid dirty data, I want them to confirm that the form is correct before submitting the record.
Problem is, as soon as I input ANYTHING on the form, access creates and saves a record.
I want the record to ONLY be saved and committed if the user clicks 'Submit'. If they click close or exit the application, I do not want that partially completed record in the database.
Without using an unbound form and calling an insert function, is there a simple solution?
An autonumber is there to be unique, not sequential. If you need a sequential number, do not use autonumber. Autonumber should never be shown to the user. It can never be relied upon to be anything but unique, and if you mess about enough, not even that.
Private Sub Form_BeforeUpdate(Cancel As Integer)
If Me.AText = "Invalid" Then
Me.Undo
Cancel = True
End If
End Sub
Note that a form with a subform may not work with undo, because the record is committed on change from the subform to the main form and vice versa and it all gets quite complicated.
Remou's method is definitely the quickest, here is another based on my comment ;)
Option Explicit
Private blnGood As Boolean
Private Sub cmdSubmit_Click()
blnGood = True
Call DoCmd.RunCommand(acCmdSaveRecord)
blnGood = False
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
If Not blnGood Then
Cancel = True
Call MsgBox(Prompt:="click submit to save the record", Title:="Before Update")
End If
End Sub
You can use the following code to create a clear button in case the user made an errors and wants to clear the entire form and start over.
Private Sub btnClear_Click()
If Me.Dirty = True Then
DoCmd.RunCommand acCmdUndo
Exit Sub
End If
End Sub`
I sometimes find the before_update event to act weird so I generally disable the close (x) button in properties and add a close button of my own that prompts the user if he wants to abandon the data on screen.
Private Sub close_Click()
Dim Answer As Integer
If Me.Dirty = True Then
Dim Response As Integer
' Displays a message box with the yes and no options.
Response = MsgBox(Prompt:="Do you wish to discard data?", Buttons:=vbYesNo)
' If statement to check if the yes button was selected.
If Response = vbYes Then
DoCmd.RunCommand acCmdUndo
DoCmd.Close
Else
Me.Clear.SetFocus
End If
Else
' The no button was selected.
DoCmd.Close
End If
End Sub