I have an Excel tool that is used daily by a lot of people - each month they are expected to run a macro to report data to a central point.
Is there a way that I can call a macro such as this as the document is opened?
How would you go about doing this in the Workbook_Open event? Would you make it optional or give users no choice (i.e. auto submit on said day?)
This would potentially help me from chasing people up monthly
As a side note, if I gave users the 'option' - is there a less crude way of reminding them? For example if answer = vbNo Then range("A1") = "1" and then having an if statement within workbook open until they press yes which would set range ("A1") to 0 or similar?
Thanks
You are very much on the right track.
Make a reference worksheet in the workbook with the end-of-month dates listed in A1:A12, then place this code in the Workbook_Open event.
Private Sub Workbook_Open()
Dim dDate as Date
dDate = Application.WorksheetFunction.EoMonth(Date, 0)
With Worksheets("reference")
Dim rFound As Range
Set rFound = .Range("A1:A12").Find(dDate, lookat:=xlWhole, LookIn:=xlValues)
If Not rFound Is Nothing Then
If rFound.Offset(, 1) = 0 Then
Dim ret As Variant
ret = MsgBox("Would you like to submit data now?", vbYesNo)
If ret = vbYes Then
'run macro to submit data
rFound.Offset(, 1) = 1
'you may want to set this cell to 1 also if the user submits data manually so it knows for next time
Else
'set to zero to check for next time
rFound.Offset(, 1) = 0
End If
End If
End If
End With
End Sub
This is optional for the user, but will repeatedly ask them each time they open the book until they do submit for that month. Then it will wait until next month to ask again.
The problem with making it auto-submit on a given day is that what if the user never opens the workbook on said day?
Related
I have this code but it only work for my first row.
It is suppose to look if the checkbox on B, C or D is checked, and if so, a date + username will automaticaly fill in F and G.
here is a picture of my table:
This is what my code looks like:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Range("B2") Or Range("C2") Or Range("D2") = True Then
Range("G2").Value = Environ("Username")
Range("F2").Value = Date
Else
Range("F2:G2").ClearContents
End If
End Sub
Enter this code in a regular module, select all your checkboxes and right-click >> assign macro then choose ReviewRows.
This will run the check whenever a checkbox is clicked - a bit of overhead since all rows will be checked, but should not be a big deal.
Sub ReviewRows()
Dim n As Long
For n = 1 To 100 'for example
With Sheet1.Rows(n)
If Application.CountIf(.Cells(2).Resize(1, 3), "TRUE") > 0 Then
If Len(.Cells(6).Value) = 0 Then 'only enter if currently empty?
.Cells(6) = Date
.Cells(7) = Environ("Username")
End If
Else
.Cells(6).Resize(1, 2).ClearContents
End If
End With
Next n
End Sub
If you want to be more precise then Application.Caller will give you the name of the checkbox which was clicked, and you can use that to find the appropriate row to check via the linkedCell.
Sub ReviewRows()
Dim n As Long, shp As CheckBox, c As Range, ws As Worksheet
Set ws = ActiveSheet
On Error Resume Next 'ignore error in case calling object is not a checkbox
Set shp = ActiveSheet.CheckBoxes(Application.Caller) 'get the clicked checkbox
On Error GoTo 0 'stop ignoring errors
If Not shp Is Nothing Then 'got a checkbox ?
If shp.LinkedCell <> "" Then 'does it have a linked cell ?
With ws.Range(shp.LinkedCell).EntireRow
If Application.CountIf(.Cells(2).Resize(1, 3), "TRUE") > 0 Then
If Len(.Cells(6).Value) = 0 Then 'only enter if currently empty?
.Cells(6) = Date
.Cells(7) = Environ("Username")
End If
Else
.Cells(6).Resize(1, 2).ClearContents
End If
End With
End If 'has linked cell
End If 'was a checkbox
End Sub
However this appraoch is sensitive to the exact positioning of your checkbox
You have a long way to go!
Unfortunately, If Range("B2") Or Range("C2") Or Range("D2") = True Then is beyond repair. In fact, your entire concept is.
Start with the concept: Technically speaking, checkboxes aren't on the worksheet. They are on a layer that is superimposed over the worksheet. They don't cause a worksheet event, nor are they responding to worksheet events. The good thing is that they have their own.
If Range("B2") Or Range("C2") Or Range("D2") = True Then conflates Range with Range.Value. One is an object (the cell), the other one of the object's properties. So, to insert sense into your syntax it would have to read, like, If Range("B2").Value = True Or Range("C2").Value = True Or Range("D2").Value = True Then. However this won't work because the trigger is wrong. The Worksheet_Change event won't fire when when a checkbox changes a cell's value, and the SelectionChange event is far too common to let it run indiscriminately in the hope of sometimes being right (like the broken clock that shows the correct time twice a day).
The answer, therefore is to capture the checkbox's click event.
Private Sub CheckBox1_Click()
If CheckBox1.Value = vbTrue Then
MsgBox "Clicked"
End If
End Sub
Whatever you want to do when the checkbox is checked must be done where it now shows a MsgBox. You can also take action when it is being unchecked.
I am trying to judge whether today's date is later than any date specified in a column of a worksheet everytime the workbook is opened to decide whether will give user a reminder. I use debug.print to make sure the condition I set was correct but there was no reminder popped out everytime I open the file. Could anyone point out where did I make the mistake? Thanks.
I used cdbl to convert date to double and compare between today and the date in the column. If anyone of them is earlier than today it will give user a reminder.
Private Sub UserForm_Initialize()
Dim i As Integer
Dim cnt As Integer
Count = 0
With ThisWorkbook.Worksheets("Gun Inventory")
For i = 2 To .Cells(Rows.Count, "A").End(xlUp).row
If Int(CDbl(Date)) > Int(CDbl(.Cells(i, "J").Value)) Or Int(CDbl(Date)) = Int(CDbl(.Cells(i, "J").Value)) Then
.Cells(i, "I") = "Needs Test"
Count = Count + 1
Else: Exit Sub
End If
Next i
If Count <> 0 Then MsgBox "You have guns needs check ASAP. Please review 'gun inventory' tab to see which gun it is."
End With
End Sub
I expect there should be a reminder but there is nothing. Not even an error, which is weird.
If you want to execute that portion of code when the workbook is opened you should use:
Sub Auto_Open()
Insert code here
End Sub
Or use Workbook_Open() but you will have to add it in ThisWorkbook class.
I am experiencing an odd bug on Excel. I have a macro that shows a non-modal userform when I press CTRL+m (Macro shortcut). Every once in a while, and it's not that frequent (Shows up once or twice during the day, I use the macro every 5 minutes or so), Excel won't run the macro, won't show the userform and will just beep (as in "mistake, cannot proceed executing code").
I went into the Macro window to try to press "Run" and manually execute, but all buttons are disabled, except for "Create". If you click it, it says the macro name is not valid. As you can see in the screenshot below, the name of the macro shows the instance where the code is (Sheet1 of the workbook).
Sometimes it can be fixed by saving the workbook and just re-trying, but sometimes it doesn't; when it doesn't, I run a different macro (by double clicking a specific column) that shows a modal userform, and executing its code. Then my first macro returns to normal.
Any help will be very much appreciated.
Edit: Adding the code as requested in the comments
Sub ShowCommentWindow()
Dim myCell As Range
Dim companyColumn As Long
Dim wbk as Workbook
Dim company as String
Dim phone as Long
Set wbk = ActiveWorkbook
For Each myCell In wbk.Worksheets(1).Range("A1:Q1")
If myCell.Text = "Company" Then
companyColumn = myCell.Column
company = ActiveCell.Text
phone = ActiveCell.Offset(0, 4).Value
Exit For
End If
Next myCell
If ActiveCell.Column = companyColumn Then
If EmailForm.Visible Then
GoTo ExitProc
Else
If Not ActiveCell.Row < 4 Then
ActiveWindow.ScrollRow = ActiveCell.Row - 3
Else
ActiveWindow.ScrollRow = ActiveCell.Row
End If
If CommentWindow.Visible Then
CommentWindow.AddButton.SetFocus
CommentWindow.CommentBox.SetFocus
Exit Sub
Else
CommentWindow.Show
ManageComments
AddComment
End If
End If
End If
ExitProc:
End Sub
Edit2: Posting more code, for QueryClose:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
Dim myCell As Range
Dim isCompany As String
If Not CommentWindow.CommentBox.Text = CommentWindow.TextCopy.Text Then
saveConf = MsgBox("Changes have not been saved yet. Do you want to save?", vbExclamation + vbYesNoCancel + vbDefaultButton2, "Save changes?")
If saveConf = vbYes Then
Call SaveComment
GoTo ExitProc
ElseIf saveConf = vbCancel Then
changed = True
Cancel = 1
CommentWindow.AddButton.SetFocus
CommentWindow.CommentBox.SetFocus
'CommentWindow.CommentBox.Text = CommentWindow.TextCopy.Text
Else
CommentWindow.TextCopy.Text = CommentWindow.CommentBox.Text
GoTo ExitProc
End If
Else
If Not changed = True Then
GoTo ExitProc
End If
End If
ExitProc:
End Sub
Seems like the issue is not unloading the forms from ( Unload(UserForm) )
This leads to a memory leak.
Even the official documentation -this refers to Access, but, should behave the same for Excel (there's no Form object or userform documentation there)- state the Lifecycle is Unload->Deactivate->Close, and this should happen when you close the userform as well, daily usage has shown that Unload if not stated may not be triggered when closing the userform.
The lifecycle is not that strictly monitored sometimes, but, that may lead to memory leaks and strange behaviors, always when working with objects you shouldn't rely that garbage collector will clean them if not specified. Probably adding something to confirm that terminate is being correctly handled will be helpful.
EDIT
If you're having problems remembering the unload -or still having problems with memory-, it will be a good practice to do the following:
Sub MyMainProcess()
Dim myform As UserForm1: Set myform = UserForm1 'this is your UserForm name
myform.Show
'my stuff needed...
Unload myform
Set myform = Nothing
End Sub
Unload and Nothing to clean as much as possible with coding
I see that you're calling an "outside" macro (it's not within the active workbook) - Is it possible that then those roughly 2 times a day that it doesn't work that workbook (Database 2 Lumber.xlsm) is being used by someone else at that time (eight running that, or another macro?).
If so, What I have done before is save a local copy of the workbook each time the macro is run
Quick version: I've got broken links in a file I'm working with because they're pointing to someone else's hard drive. A macro went wrong in someone else's file that converted all formulas to text by appending an apostrophe before the formula. I wrote a macro to fix this, but there are a ton of external links in the file. The macro essentially changes a formula from the first line to the second line below, doing nothing more than removing the unnecessary apostrophe.
1) '='C:\OtherPersonsFolderPath\[FileName.xlsm]Sheet1'!A1
2) ='C:\OtherPersonsFolderPath\[FileName.xlsm]Sheet1'!A1
If I do this manually, Excel opens a dialog box asking me to "Update Values" in FileName.xlsm by pointing to the right file. I don't want to update the file path though: I plan to give this back to the original owner of the file with all paths in tact, sans apostrophes. If I hit the "cancel" button on that dialog box, I get the intended effect: The formula updates to what I need, and the value changes to whatever it used to be back when it was a working link. It works fine if I manually hit "cancel" on the box every time it pops up, but I've got thousands of cells to iterate through across dozens of sheets. I need a way to tell VBA to say "cancel" in that box, or prevent the box from appearing in the first place. Any ideas? My code is below:
Public Sub MyBugFix()
Application.Calculation = xlCalculationManual
'Note that I unsuccessfully tried options like "ThisWorkbook.UpdateLinks = xlUpdateLinksNever" and "Application.DisplayAlerts = False" here
Dim WS_Count As Integer
Dim I As Integer
WS_Count = ActiveWorkbook.Worksheets.Count
For I = 1 To WS_Count
Sheets(I).Visible = True
Sheets(I).Select
Range("A1:BZ400").Select
'Simple fix for embedded apostrophes in formulas (e.g., an equals sign in an IF statement)
Selection.Replace What:="'=", Replacement:="=", LookAt:=xlPart, _
SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
'More complex fix for apostrophes at the start (they're special characters, so the find/replace trick doesn't work)
Dim myRng As Range
Dim myCell As Range
Set myRng = Nothing
On Error Resume Next
Set myRng = Intersect(Selection, _
Selection.Cells.SpecialCells(xlCellTypeConstants))
On Error Resume Next
For Each myCell In myRng.Cells
If myCell.PrefixCharacter <> "" Then
myCell.Value = "" & myCell.Text
On Error Resume Next
End If
Next myCell
Next I
Application.Calculation = xlCalculationAutomatic
End Sub
I found a solution here: http://www.mrexcel.com/forum/excel-questions/506273-turn-off-update-values-dialog-box.html so I can't claim any credit for it!
Put this before you edit the formula ...
ThisWorkbook.UpdateLinks = xlUpdateLinksNever
Then turn it back on after you have made your edits...
ThisWorkbook.UpdateLinks = xlUpdateLinksAlways
This solved a similar problem for me, where I was using VBA to write cell formula containing references to other spreadsheets. All credit goes to AlphaFrog on the MrExcel forum!
I found out a combination of commands:
ThisWorkbook.UpdateLinks = xlUpdateLinksNever
Application.DisplayAlerts = False
'your macro
ThisWorkbook.UpdateLinks = xlUpdateLinksAlways
Application.DisplayAlerts = True
Best Regards
Disabling the Select Sheet or Update Values dialog:
This short macro's use of the Range.TextToColumns method should quickly sweep away any prefixing tick (aka ', single-quote or Chr(39)) .PrefixCharacters from an entire workbook. While the operation is intended to target formulas that have been commented out of operation with a ', it will also revert text that looks like numbers to true numbers.
Sub Clean_Prefix_Ticks()
Dim w As Long, c As Long
With ActiveWorkbook '<- set this workbook properly if necessary
For w = 1 To Worksheets.Count
With Worksheets(w).UsedRange
For c = 1 To .Columns.Count
With .Columns(c)
If CBool(Application.CountA(.Cells)) Then _
.Cells.TextToColumns Destination:=.Cells(1), _
DataType:=xlFixedWidth, FieldInfo:=Array(0, 1)
End With
Next c
End With
Next w
End With
End Sub
The bulk nature of removing the Range.PrefixCharacter property allows the Text-to-Columns operation to sweep through each worksheet's .UsedRange without raising the external reference Select Sheet dialog.
If there was sufficient need, each column could easily be examined before the operation took place. The above code was written to handle a workbook-wide sweep.
Caveat: The TextFileColumnDataTypes property is set to xlGeneralFormat. Be advised that you may lose some forms of special cell formatting that you have assigned; especially text formatting on characters within a string (e.g. Range .Characters property).
Go to Data/edit link/Startup Prompt and then click on
Don't display the alert and don't update automatic links.
I have been trying to fix a login problem but I cannot find a solution. When both login and pass fail, an error message starts a countdown without letting the user manifest another opinion.
QUESTION 1: Can anyone please make the necessary corrections without altering too much the given code structure and explain?
QUESTION 2: What code would turn the "User1" text into bold at the moment the access is granted?
QUESTION 3: What command would disable the "X" on the top right-hand corner of the msg form?
Thank you in advance
Here it is what I could do
¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨
Private Sub BtOK_Click()
Dim User1 As String
Dim count As Integer
count = 3
MM:
If EDBoxlogin.Value = "admin" And EDBoxpass.Value = "1234" Then
User1 = Application.UserName
MsgBox "welcome" & User1 & " !", vbExclamation, "Access Granted"
Sheets("Plan1").Visible = xlSheetVisible
Unload Me
Else
If EDBoxlogin.Value = "" Or EDBoxpass.Value = "" Then
MsgBox "Please, fill in the fiels 'login' and 'pass'", vbExclamation + vbOKOnly, "Access denied : incomplete information"
Else
If count >= 0 Then
MsgBox "Login and pass are incorrect! You have " & count & " more trial(s)", vbExclamation + vbOKOnly, "Access denied"
EDBoxlogin.Value = "" And EDBoxpass.Value = ""
' I want to delete previous text in the editbox fields
count = count - 1
GoTo MM
Else
ThisWorkbook.Close
End If
End If
End If
End Sub
If you don't really need to know which user is opening the workbook, consider using Excel's built-in password security function. Also, you should encrypt the contents of the file also using Excel's built-in functions, or anyone can open the file with a text editor and find the userID and password listed in your code.
If you must use a login form, and I've also had to do so in the past, the following code builds on what you did by adding a user list to a hidden worksheet Users. Column A in that sheet needs to be the user names, B contains the passwords. This worksheet also uses cell D1 to track failed login attempts. Using variables in code for this sort of thing is tough ... you have to make them Public and if there are any errors when running code, it will lose its value, then bad things can happen.
The code also references another sheet, SplashPage. This allows you to hide Project1 when the user exits the workbook. The code I wrote handles the hide/unhide process when the file is opened or closed.
I don't know a way to turn off the close box in a user form. I've added code to reject the login if a user does that.
Happy coding.
'Module: frmLogin
Private Sub BtOK_Click()
Dim User1 As String
Dim Passwd As Variant
Sheets("Users").Range("D2").Value = False
User1 = EDBoxlogin.Value
Passwd = getPassword(User1)
If User1 <> "" And Passwd <> "" And EDBoxpass.Value = Passwd Then
Sheets("Users").Range("D2").Value = True
MsgBox "Welcome " & User1 & "!", vbExclamation, "Access Granted"
With Sheets("Plan1")
.Visible = xlSheetVisible
.Activate
End With
Sheets("SplashPage").Visible = xlSheetVeryHidden
Unload Me
Exit Sub
Else
Sheets("Users").Range("D1").Value = Sheets("Users").Range("D1").Value - 1
If Sheets("Users").Range("D1").Value > 0 Then
MsgBox "Login and pass are incorrect! You have " & Sheets("Users").Range("D1").Value & _
" more trial(s)", vbExclamation + vbOKOnly, "Access denied"
EDBoxpass.Value = ""
With EDBoxlogin
.Value = ""
.SetFocus
End With
' I want to delete previous text in the editbox fields
Exit Sub
End If
End If
UserForm_Terminate
End Sub
Private Sub UserForm_Terminate()
If Sheets("Users").Range("D2").Value <> True Then
MsgBox "Login cancelled, goodbye!"
doWorkbookClose
End If
End Sub
'Module: ThisWorkbook
Private Sub Workbook_BeforeClose(Cancel As Boolean)
doWorkbookClose
End Sub
Private Sub Workbook_Open()
On Error Resume Next
Sheets("Users").Range("D1").Value = 3
With Sheets("SplashPage")
.Visible = xlSheetVisible
.Activate
End With
Sheets("Plan1").Visible = xlSheetVeryHidden
Sheets("Users").Visible = xlSheetVeryHidden
ThisWorkbook.Save
frmLogin.Show
End Sub
'Module: Module1
Function getPassword(strVarib As String) As Variant
Dim r As Long
Dim sht As Worksheet
Dim rng As Range
On Error GoTo ErrorHandler
Set sht = Sheets("Users")
Set rng = sht.Range("A:A")
r = WorksheetFunction.Match(strVarib, rng, 0)
getPassword = sht.Cells(r, 2).Value
Exit Function
ErrorHandler:
getPassword = Empty
End Function
Sub doWorkbookClose()
On Error Resume Next
With Sheets("SplashPage")
.Visible = xlSheetVisible
.Activate
End With
Sheets("Plan1").Visible = xlSheetVeryHidden
Sheets("Users").Visible = xlSheetVeryHidden
ThisWorkbook.Save
End Sub
[begin Q&A]
Luiz, I've answered your edits below.
'Q: What Passwd does?
'Module: frmLogin
....
Passwd = getPassword(User1)
A: It gets the password value matching the value of User1. Here's the whole function for context:
Function getPassword(strVarib As String) As Variant
Dim r As Long
Dim sht As Worksheet
Dim rng As Range
On Error GoTo ErrorHandler
Set sht = Sheets("Users")
Set rng = sht.Range("A:A")
r = WorksheetFunction.Match(strVarib, rng, 0)
getPassword = sht.Cells(r, 2).Value
Exit Function
ErrorHandler:
getPassword = Empty
If User1 does not exist then WorksheetFunction.Match throws an error and code execution will jump to ErrorHandler:.
'Q: Does Empty mean that the cell is not with zeros or spaces, but completely blank instead?
A: Empty refers to a Variant variable type that is set to its default value. getPassword could just as easily return the boolean False or integer 0 because those are the default values for those types. It's actually not strictly necessary to set getPassword to anything here ... it's just my personal practice to be explicit.
Since IsEmpty(celFoo) is a valid test for whether a cell is empty or not, you might want to return False instead of Empty to avoid ambiguity.
'Q: Can you explain these two lines below in detail?
Set sht = Sheets("Users")
Set rng = sht.Range("A:A")
A: It's just habit. The alternative would be to elminate those variable assignments and rewrite this line:
r = WorksheetFunction.Match(strVarib, rng, 0)
as:
r = WorksheetFunction.Match(strVarib, Sheets("Users").Range("A:A"), 0)
which is messier to type. Especially if we're going to be doing other things on that sheet with that range in the same routine. Which we are in the next block of code ...
'Q: Important to explain these three lines below in detail too [why 0?, To where (r,2) points to?]
r = WorksheetFunction.Match(strVarib, rng, 0)
getPassword = sht.Cells(r, 2).Value
Exit Function
A: To review, worksheet Users contains user IDs in column A, and their passwords in column B. There can be as many users as there are rows in a worksheet.
- rng is column A as set above.
- 0 means find an exact match for strVarib and throw an error if not match is found.
- If we find a match, r will be set to the row number where the value in column A is equal to our input parameter, strVarib.
- So, sht.Cells(r, 2).Value is the password value in column B (column 2) for the UserID.
'Q: Why the need to call a splashpage? What it contains?
A: You don't necessarily need one, but if you really want to secure your workbook it's good practice. Let's say that it contains sensitive information that you don't want unauthorized user to see. At the very least you would:
Encrypt the worbook using native Excel functionality.
Password protect your VBA project using native functionality. This keeps savvier users from reading your code and making the xlSheetVeryHidden sheets Users and Plan1 visible to their prying eyes.
Now, you can't hide all sheets in a workbook at the same time, at least one needs to be visible at any given time ...
... so I've created a third sheet called SplashPage that doesn't contain any sensitive information. And that means I can hide all of the other worksheets until the user enters a valid UserID and password in frmLogin.
SplashPage can contain whatever you want. You can call it whatever you want. Typically, mine says something like:
Welcome to the Enemies List Application!
Only authorized users may access this workbook.
If you're seeing this page and no login form is visible
it means you've disabled the macros in this workbook.
Please make sure macro security is set to "Medium"
then close Excel entirely, reopen this file
and select "Enable Macros" when prompted.
If you attempt to view or modify this file without proper
authorization you will be added to the list herein.
-[Signed] Richard M. Nixon
A really really secure workbook would not contain the users and passwords in a hidden sheet. In fact, I never do this. Most of my apps are database driven, and I authenticate users against both the domain and a custom table in the application database. This effectively keeps anyone from using it unless they're onsite and connected to the network. I also usually flush all the data from the relevant worksheets when the user closes the workbook to a) keep the file size smaller and b) keep sensitive data from being stored in it and taken offsite. But that's beyond the original scope of your question.
'Why is [the following] necessary? What is being saved? Purpose?
Private Sub Workbook_BeforeClose(Cancel As Boolean)
ThisWorkbook.Save
A: There are two scenarios for closing the application: 1) a failed login attempt and 2) a successful login by a user who has finished making changes.
Take case (2) first. We want to hide all the sensitive information before closing so that the next person who opens the file only sees SplashPage and the login form. We know the user is closing the workbook because we have this code in the ThisWorkbook module BeforeClose event script:
'Module: ThisWorkbook
Private Sub Workbook_BeforeClose(Cancel As Boolean)
doWorkbookClose
End Sub
All it does is call this subroutine in Module1:
Sub doWorkbookClose()
On Error Resume Next
With Sheets("SplashPage")
.Visible = xlSheetVisible
.Activate
End With
Sheets("Plan1").Visible = xlSheetVeryHidden
Sheets("Users").Visible = xlSheetVeryHidden
ThisWorkbook.Save
End Sub
Since our close routine makes changes to the workbook to hide sensitive information, those changes need to be saved. If ThisWorkbook.Save wasn't there, Excel would prompt the user if they wanted to save "their" changes. Which is annoying at best, confusing at worst, because most users will have already pressed "Save" before closing. And if we give them the option here now to close without saving, then we run the risk of all those sensitive worksheets we've just made xlVeryHidden visible to the next user. And that next user could be a bad guy who knows how to disable macros (or anyuser who simply has macro security set above Medium) which means that the following code wouldn't run:
Private Sub Workbook_Open()
On Error Resume Next
Sheets("Users").Range("D1").Value = 3
With Sheets("SplashPage")
.Visible = xlSheetVisible
.Activate
End With
Sheets("Plan1").Visible = xlSheetVeryHidden
Sheets("Users").Visible = xlSheetVeryHidden
ThisWorkbook.Save
frmLogin.Show
End Sub
which is my semi-paranoid-self trying to make it as sure as possible that the next user opening this file doesn't see something I don't want them to.
Note that none of this secuity is bomb-proof. It will lock out most average Excel users that you don't want in it, but someone who knows more about VBA than I do could probably find a way in.
Yes, that was an invitation. :)