Excel 2013 toggle read only - vba

I have an application that I've written in Excel 2003 and I have recently upgraded to Excel 2013. In the workbook_open event I set the workbook to read-only ActiveWorkbook.ChangeFileAccess xlReadOnly and have used the Toggle Read Only button to switch between read\write and read only. In Excel 2003 when switching file mode the workbook would toggle as expected. When I run it in 2013 as an .xlsm the Workbook_Open Event is called after switching the file status and it becomes read only again.
Private Sub Workbook_Open()
If ActiveWorkbook.ReadOnly = False Then
ActiveWorkbook.Saved = True
ActiveWorkbook.ChangeFileAccess xlReadOnly
End If
End Sub

It is possible achieve the results you are after without changing the FileAccess to read only. You can use the Workbook_BeforeSave and Workbook_Beforeclose events to control one's ability to save the workbook. I have provided a complete code example below that I believe would suit your needs. You could use a toggle button or any method you choose to run the subMakeItSaveable and subMakeItUnSaveable, or you could implement that functionality in a single routine.
The funUpdateCustomDocumentProperty function writes a Boolean value to the workbooks custom properties to toggle the ability to save the sheet. Please note that this custom property doesn't do anything except provide a place to store a value that isn't in either code or in the sheet. This provides a handy method of persisting data that our code needs when the code isn't running.
The code I used follows:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
ThisWorkbook.Saved = True
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If ThisWorkbook.CustomDocumentProperties("SaveMyChanges").Value Then
SaveAsUI = True
Cancel = False
Else
SaveAsUI = False
Cancel = True
myTestValue = MsgBox("Read Only Workbook. Save Not Allowed.", vbInformation, "Operation Aborted")
End If
End Sub
Private Sub Workbook_Open()
myTestValue = funUpdateCustomDocumentProperty("SaveMyChanges", False, msoPropertyTypeBoolean)
End Sub
Public Function funUpdateCustomDocumentProperty(strPropertyName As String, _
varValue As Variant, docType As Office.MsoDocProperties) As Boolean
'Function returns true if custom property was added, false if it already exists
'Originally a sub built by Peter Albert
'http://stackoverflow.com/users/1867581/peter-albert
On Error Resume Next
funUpdateCustomDocumentProperty = False
ThisWorkbook.CustomDocumentProperties(strPropertyName).Value _
= varValue
If Err.Number > 0 Then
ThisWorkbook.CustomDocumentProperties.Add _
Name:=strPropertyName, _
LinkToContent:=False, _
Type:=docType, _
Value:=varValue
funUpdateCustomDocumentProperty = True
End If
End Function
Public Sub subMakeItSaveable()
myTestValue = funUpdateCustomDocumentProperty("SaveMyChanges", True, msoPropertyTypeBoolean)
End Sub
Public Sub subMakeItUnSaveable()
myTestValue = funUpdateCustomDocumentProperty("SaveMyChanges", False, msoPropertyTypeBoolean)
End Sub

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.

Hide sheets VBA - Excel bug?

I am currently doing a VBA code that needs to hide some sheets when the Excel file is closed and almost everything is working fine expects when I do the following steps:
make some change/insert data in the sheets
click the save button
make another change (that I do not want to save)
click to close the file and click not to save it
The problem is that I hide the sheets but since I do not save the file (because i do not want to save the changes made at step 3) the sheets are not hidden when I reopen the file. I cannot do this with the Workbook_open method because it is not allowed (at my project).
To do this I am rewriting the beforeclose method, as follows:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Dim Msg As String
Dim ireply As Integer
If Not Me.Saved Then
Msg = "Do you want to save the file?"
ireply = MsgBox(Msg, vbQuestion + vbYesNoCancel)
Select Case ireply
Case vbYes
Call hidesheets
Me.Save
Case vbNo
Me.Saved = True
Application.Quit
Case vbCancel
Cancel = True
Exit Sub
End Select
Else
Call hidesheets
Me.Save
End If
End Sub
Sub hidesheets()
ThisWorkbook.Sheets("Cars").Visible = xlVeryHidden
ThisWorkbook.Sheets("Brands").Visible = xlVeryHidden
ThisWorkbook.Sheets("Models").Visible = xlVeryHidden
ThisWorkbook.Sheets("Price").Visible = xlVeryHidden
End Sub
My questions is, it is possible just to save the hidden sheets configurations/settings without saving the information/data changed/inserted by the user?
PS: when I save the file and make any change the code works fine, i.e. hides the sheets.
Thank you all in advance
Regards
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
ThisWorkbook.Sheets("Cars").Visible = xlVeryHidden
ThisWorkbook.Sheets("Brands").Visible = xlVeryHidden
ThisWorkbook.Sheets("Models").Visible = xlVeryHidden
ThisWorkbook.Sheets("Price").Visible = xlVeryHidden
End Sub
You have to do the other way around
1) Set your workbook having those four sheets as very hidden per default
set them as such and then save your workbook to have it assume its default configuration
2) When you open it you make those sheets visible
Private Sub Workbook_Open()
ThisWorkbook.Sheets("Cars").Visible = True
ThisWorkbook.Sheets("Brands").Visible = True
ThisWorkbook.Sheets("Models").Visible = True
ThisWorkbook.Sheets("Price").Visible = True
End Sub
3) When you close it, you set those sheets back invisible
Private Sub Workbook_BeforeClose(Cancel As Boolean)
'Your code to be processed upon closing the sheet
'...
Call hidesheets '<--| hide your sheets
End Sub

How can I run a macro as a workbook opens for the first time only?

I've got a workbook which runs a macro to show the userform Open1 as it opens, using the (very basic) code:
Private Sub Workbook_Open()
Open1.Show
End Sub
This does its job fine - each time I open the workbook, the userform pops up and runs perfectly.
But, I want the userform to appear the first time the workbook is opened only. Is there a way to allow this to happen?
You could use a dummy module which gets deleted the first time you open the spreadsheet...
Something like:
If ModuleExists("DummyModule") Then
Open1.Show
DoCmd.DeleteObject acModule, "DummyModule"
End If
Function ModuleExists(strModuleName As String) As Boolean
Dim mdl As Object
For Each mdl In CurrentProject.AllModules
If mdl.Name = strModuleName Then
ModuleExists = True
Exit For
End If
Next
End Function
Update: as stated, DoCmd isn't used in excel vba. That will teach me to write code without testing it!
The following updated code will work, but in order to access the VB environment, excel needs to be trusted.
There is a setting in the Trust Center>Macro Settings that you can tick for this code to work under Developer Macro Settings
As such, this may not be the way to go as it opens up the possibility of security issues...
Sub RemoveModule()
If ModuleExists("DummyModule") Then
Open1.Show
Dim vbCom As Object: Set vbCom = Application.VBE.ActiveVBProject.VBComponents
vbCom.Remove VBComponent:=vbCom.Item("DummyModule")
End If
End Sub
Function ModuleExists(strModuleName As String) As Boolean
Dim mdl As Object
For Each mdl In Application.VBE.ActiveVBProject.VBComponents
If mdl.Name = strModuleName Then
ModuleExists = True
Exit For
End If
Next
End Function
Try this:
If Sheets("Hide").Cells(1,1) = "1" Then
Open1.Show
Sheets("Hide").Cells(1,1) = "0"
End if
You must create the sheet Hide, and give the cell A1 the value 1, in that case the form will be shown.
After you create the sheet, hide it with this
Sheets("Hide").Visible = xlVeryHidden
And show it with this
Sheets("Hide").Visible = True
Here's an alternative bit of code that will persist between saves and allow you to reset it. No need to create a hidden sheet.
Put this in a module (invoke the DisplayFormIfFirstTime from your Workbook_Open event handler....)
Option Explicit
Private Const cMoniker As String = "FormHasBeenDisplayed"
Private Sub DisplayFormIfFirstTime()
If HasBeenOpened = False Then DisplayForm
End Sub
Public Sub DisplayForm()
MsgBox "Ok, its not a form but a dialog box...", vbInformation
End Sub
Public Function HasBeenOpened() As Boolean
Dim oName As Name
On Error Resume Next
Set oName = Application.Names(cMoniker)
On Error GoTo 0
If Not oName Is Nothing Then
HasBeenOpened = True
Else
Call Application.Names.Add(cMoniker, True, False)
End If
End Function
'Call this to remove the flag...
Public Sub ResetOpenOnce()
On Error Resume Next
Application.Names(cMoniker).Delete
End Sub
Based on the idea supplied by PaulG, I have coded an upgrade that will check for the name and if not found run a function, add the name and save the workbook for a more seemless approach to this problem...
Placed in ThisWorkbook
Private Sub Workbook_Open()
Run "RunOnce"
End Sub
Placed in a module
Sub RunOnce()
Dim Flag As Boolean: Flag = False
For Each Item In Application.Names
If Item.Name = "FunctionHasRun" Then Flag = True
Next
If Flag = False Then
Call Application.Names.Add("FunctionHasRun", True, False)
Application.DisplayAlerts = False
ActiveWorkbook.Save
Application.DisplayAlerts = True
Call RunOnceFunction
End If
End Sub
Private Function RunOnceFunction()
Open1.Show
End Function
Sub ResetRunOnce()
For Each Item In Application.Names
If Item.Name = "FunctionHasRun" Then
Application.Names.Item("FunctionHasRun").Delete
Application.DisplayAlerts = False
ActiveWorkbook.Save
Application.DisplayAlerts = True
End If
Next
End Sub

Detect which save function the user chooses in Office2007 using Word VBA

I'm writing a custom Word template which does some processing upon saving the current document.
In Office2007, to save a document you can use the Save and the Save As functions, and handle the events with the FileSave and FileSaveAs macros.
But when the user hovers over the SaveAs option, other sub-options are displayed: Save as a document, as a Word template, as Word 97-2003 document, etc. These sub-options don't seem to have their own events, but I'd like to know when the user uses them.
So I came up with the idea to use the DocumentBeforeSave event, but then I still have to figure out if the save occured with the standard Save/SaveAs options or with the sub-options.
I thought about setting a variable to True in the Save/SaveAs functions, which the DocumentBeforeSave event would check to see if one of the normal save methods occured, then it would set the variable back to False.
But after experimenting with different methods, I can't figure out how I can pass the value of a variable between ThisDocument and the Class Module which has the BeforeSave event.
Any ideas? Thanks!
Edit: Example code that doesn't work:
ThisDocument:
Public pSSave As Boolean
Public Property Get SSave() As Boolean
SSave = pSSave
End Property
Public Property Let SSave(Value As Boolean)
pSSave = Value
End Property
Sub FileSave()
Me.SSave = True
If SSave = True Then
MsgBox "True"
End If
Application.ActiveDocument.Save
If SSave = True Then
MsgBox "True"
End If
End Sub
Class Module:
Private Sub App_DocumentBeforeSave(ByVal Doc As Document, SaveAsUI As Boolean, Cancel As Boolean)
If Application.ActiveDocument.SSave = False Then
MsgBox "False"
End If
End Sub
The class module registration is done properly, I won't paste the code her.
The result displayed is True, False, True while theoretically, it should be True, True.
I still miss something in your logic. In comments I thought about different-reverse logic which would go this way. This code below is a mix of my way and code you presented.
Class Module
Public WithEvents App As Word.Application
Public pSSave As Boolean 'your class variable/property
Private Sub App_DocumentBeforeSave(ByVal Doc As Document, SaveAsUI As Boolean, Cancel As Boolean)
If pSSave = False Then
MsgBox pSSave
Else
MsgBox pSSave
End If
End Sub
Module1
'class initialization
Public wrdAPP As New myClass
Sub set_References()
Set wrdAPP.App = Application
End Sub
ThisDocument Module
Private Sub Document_Open()
'to initialize public variable when open
Call Module1.set_References
End Sub
Sub FileSave()
wrdAPP.pSSave = True
Application.ActiveDocument.Save
If wrdAPP.pSSave = True Then
MsgBox "True"
End If
End Sub
I don't know which way you are going to run FileSave sub. But after it is run it pass the value to class property which you could check in your event.
Hope it would help you anyway.

How to disable the save function

I currently have a macro which does data mining and saves the workbook in the end. I intend to disable to the save function of the workbook and force the user to use the macro everytime the workbook needs to be saved. This is what I have so far but it does not seem to work. When I do this, my macro and this sub described below are both running in a loop. every time my macro tries to save the workbook, this sub is not allowing it. I basically want to force the user to use the macro to save the workbook.
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Dim NoSave
NoSave = MsgBox("Changes have to be submitted before the workbook can be saved, Proceed and submit ?", vbYesNo, "Continue?")
If NoSave = vbNo Then
Cancel = True
Else
Main
End If
End Sub
Here is an Example. Paste this in ThisWorkbook. This will not let you use the Save or the SaveAs. You can however use the macro SaveThisFile to save the workbook. Please amend it to suit your needs.
Option Explicit
Dim SaveByCode As Boolean
Const msg As String = "Please use the macro to save the file"
Private Sub Workbook_BeforeClose(Cancel As Boolean)
If Me.Saved = False And SaveByCode = False Then
MsgBox msg, vbExclamation, "Unable to save"
Cancel = True
End If
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Application.EnableEvents = False
If SaveByCode = True Then
SaveThisFile
Else
MsgBox msg, vbExclamation, "Unable to save"
Cancel = True
End If
Application.EnableEvents = True
End Sub
'~~> Your macro to save the file
Sub SaveThisFile()
SaveByCode = True
ThisWorkbook.Save
End Sub
NOTE: If your Save macro is in a module then remove this Dim SaveByCode As Boolean from ThisWorkbook and place Public SaveByCode As Boolean in a module.
Alternative, how about this (I misunderstood the question at first, but also wanted to give it a try since it's interesting):
Declare public boolean (exceptional) in the thisworkbook module:
Option Explicit
Public bSave As Boolean
In the event BeforeSave event:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Dim sNoSave As String
If bSave = True Then
bSave = False
Exit Sub
End If
sNoSave = MsgBox("Changes have to be submitted before the workbook can be saved, Proceed and submit ?", vbYesNo, "Continue?")
If sNoSave = vbNo Then
bSave = False
Cancel = True
Exit Sub
Else
bSave = True
Call Main(bSave)
End If
End Sub
In Main:
Option Explicit
Sub Main(bSave)
If bSave = True Then
ThisWorkbook.SaveAs Filename:="U:\Book1.xlsm", FileFormat:=xlOpenXMLWorkbookMacroEnabled
MsgBox "Main method called"
End If
End Sub